<?php

namespace controller\index;

defined('IA_ROOT') || exit();

use mvc\Controller;
use facade\Model;
use facade\View;
use facade\Util;

// 控制器
class Index extends Controller
{
    // 语言
    private $lang;
    private $langs;
    private $voice;

    // 系统属性
    private $site_url; //站点地址
    private $setting; //系统设置
    private $cdn_speed; //CDN加速
    private $action; //获取方法
    private $param; //请求参数
    private $callback; //JSONP
    private $login; //登录用户

    // 页面判断
    private $is_index; //是否首页
    private $is_category; //是否分类页
    private $is_tag; //是否标签页
    private $is_search; //是否搜索页
    private $is_detail; //是否详情页
    private $is_page; //是否页面
    private $is_404; //是否404页面

    // 分类数据
    private $categories = []; //全部分类
    private $categories_tree = []; //分类树
    private $category = []; //当前分类
    private $category_id = 0; //当前分类id

    // 标签数据
    private $tags = []; //全部标签
    private $tag = []; //当前标签
    private $tag_id = 0; //当前标签id

    // 热门文章
    private $hots = []; //热门文章

    // 文章|页面数据
    private $pages = []; //全部页面
    private $pages_tree = []; //页面树
    private $page = []; //当前页面
    private $page_id = 0; //当前页面id
    private $article; //当前文章
    private $article_id = 0; //当前文章id
    private $article_views = 0; //当前文章点击数
    private $article_likes = 0; //当前文章点赞数
    private $download = ''; //文章下载

    // 公共数据
    private $q = ''; //搜索词
    private $p = 1; //分页
    private $page_suffix = ''; //分页后缀
    private $start = 0; //第n页开始数量
    private $limit = 10; //分页数量

    // 布局数据
    private $header = []; //头部
    private $main = []; //主体
    private $aside = []; //侧栏
    private $footer = []; //底部

    // 压缩css|js
    private $css = []; //css
    private $css_url = '';
    private $js = []; //js
    private $js_url = '';

    // 初始化
    protected function initialize()
    {
        // 语言
        !session_id() && session_start();
        $this->lang = Util::lang();
        $this->langs = array_map(function ($item) {
            $item['title'] = Util::tran($item['title']);
            return $item;
        }, Util::langs());
        $this->voice = Util::voice();

        // 系统属性
        $this->site_url = Util::url(); //站点地址
        $this->setting = $this->setting(); //系统设置
        $this->cdn_speed = isset($this->setting['cdn_speed']) && $this->setting['cdn_speed'] ? 1 : 0; //CDN加速
        $this->action = Util::action(); //获取方法
        $this->param = Util::param(); //请求参数
        $this->callback = isset($this->param['callback']) && $this->param['callback'] && preg_match('/^[a-zA-Z0-9_]+$/', $this->param['callback']) ? $this->param['callback'] : '';
        $this->login = $this->checkLogin(); //登录用户

        // 页面判断
        $this->is_index = 'index' == $this->action; //是否首页
        $this->is_category = 'category' == $this->action; //是否分类页
        $this->is_tag = 'tag' == $this->action; //是否标签页
        $this->is_search = 'search' == $this->action; //是否搜索页
        $this->is_detail = 'detail' == $this->action; //是否详情页
        $this->is_page = 'page' == $this->action; //是否页面
        $this->is_404 = !$this->is_index //是否404页面
            && !$this->is_category
            && !$this->is_tag
            && !$this->is_search
            && !$this->is_detail
            && !$this->is_page;

        // 压缩css|js
        $this->css = [
            $this->cdn_speed ? 'https://fastly.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css' : 'res/bootstrap/css/bootstrap.min.css',
            $this->cdn_speed ? 'https://fastly.jsdelivr.net/npm/kindeditor@4.1.10/plugins/code/prettify.css' : 'res/kindeditor/plugins/code/prettify.css',
            'css/custom.css'
        ];
        $this->css_url = '';
        $this->js = [
            $this->cdn_speed ? 'https://fastly.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js' : 'js/jquery-1.12.4.min.js',
            $this->cdn_speed ? 'https://fastly.jsdelivr.net/npm/bootstrap@3.4.1/dist/js/bootstrap.min.js' : 'res/bootstrap/js/bootstrap.min.js',
            $this->cdn_speed ? 'https://fastly.jsdelivr.net/npm/kindeditor@4.1.10/plugins/code/prettify.js' : 'res/kindeditor/plugins/code/prettify.js',
            $this->cdn_speed ? 'https://fastly.jsdelivr.net/npm/jquery.qrcode@1.0.3/jquery.qrcode.min.js' : 'js/jquery.qrcode.min.js',
            'js/custom.js'
        ];
        $this->js_url = '';
        $this->compress();

        // 本地化公共语言串
        View::assign('lang', $this->lang);
        View::assign('langs', $this->langs);
        View::assign('voice', $this->voice);
        View::assign('localize', [
            'language' => Util::tran('语言'),
            'choose_language' => Util::tran('选择语言'),
            'mine' => Util::tran('我的'),
            'articles' => Util::tran('篇文章'),
            'no_article' => Util::tran('还没有文章'),
            'no_page' => Util::tran('还没有页面'),
            'no_category' => Util::tran('还没有分类'),
            'category_empty' => Util::tran('分类为空'),
            'no_tag' => Util::tran('还没有标签'),
            'no_hot' => Util::tran('还没有热门'),
            'no_update' => Util::tran('还没有更新'),
            'no_comment' => Util::tran('还没有评论'),
            'sure' => Util::tran('确定'),
            'info' => Util::tran('信息'),
            'anonymous' => Util::tran('匿名'),
            'say' => Util::tran('说道'),
            'delete' => Util::tran('删除'),
            'friend_link' => Util::tran('友情链接'),
            'login' => Util::tran('登录'),
            'switch_login' => Util::tran('切换登录'),
            'register' => Util::tran('注册'),
            'logout' => Util::tran('登出'),
            'switch_register' => Util::tran('切换注册'),
            'repeat_password' => Util::tran('重复密码'),
            'username' => Util::tran('用户名'),
            'input_username' => Util::tran('请输入用户名'),
            'password' => Util::tran('密码'),
            'input_password' => Util::tran('请输入密码'),
            'input_repeat_password' => Util::tran('请重输入密码'),
            'email' => Util::tran('邮箱'),
            'email_address' => Util::tran('邮箱地址'),
            'input_email_address' => Util::tran('请输入邮箱地址'),
            'email_format_error' => Util::tran('邮箱格式错误'),
            'find_password_use' => Util::tran('找回密码用'),
            'nickname' => Util::tran('昵称'),
            'phone' => Util::tran('手机'),
            'optional' => Util::tran('可选'),
            'remember_me' => Util::tran('记住我'),
            'home' => Util::tran('首页'),
            'console' => Util::tran('控制台'),
            'search' => Util::tran('搜索'),
            'logining' => Util::tran('登录中'),
            'registering' => Util::tran('注册中'),
            'reseting' => Util::tran('重置中'),
            'redirecting' => Util::tran('跳转中'),
            'forget' => Util::tran('找回密码'),
            'forget_password' => Util::tran('忘记密码'),
            'submit' => Util::tran('提交'),
            'register_email' => Util::tran('您的注册邮箱'),
            'verycode' => Util::tran('验证码'),
            'fetch_verycode' => Util::tran('获取验证码'),
            'new_password' => Util::tran('新密码'),
            'input_new_password' => Util::tran('设置新密码'),
            'confirm_password' => Util::tran('确认密码'),
            'repeat_new_password' => Util::tran('确认新密码'),
            'send' => Util::tran('发送'),
            'resend' => Util::tran('重新发送'),
            'sending' => Util::tran('发送中'),
            'username_or_email' => Util::tran('用户名或邮箱'),
            'wechat' => Util::tran('微信'),
            'scan_wechat' => Util::tran('微信扫一扫'),
            'share_email' => Util::tran('分享到邮箱'),
            'close' => Util::tran('关闭'),
            'confirm_delete' => Util::tran('确认删除吗'),
            'cancel' => Util::tran('取消'),
            'ok' => Util::tran('确定'),
            'merging' => Util::tran('正在合成，请稍后'),
            'merge_success' => Util::tran('合成成功！再次点击按钮下载。'),
            'favorite_article' => Util::tran('收藏文章'),
            'favorite_success' => Util::tran('收藏成功'),
            'favorite_cancel' => Util::tran('取消收藏'),
            'submiting' => Util::tran('提交中'),
            'say' => Util::tran('说道'),
            'reply' => Util::tran('回复'),
            'reply_comment' => Util::tran('回复评论'),
            'cancel_reply' => Util::tran('取消回复'),
            'send_comment' => Util::tran('发表评论'),
            'no_comment' => Util::tran('还没有评论'),
            'pending' => Util::tran('审核中'),
            'waiting' => Util::tran('请稍后'),
            'client' => Util::tran('客户端'),
            'size' => Util::tran('大小'),
            'download' => Util::tran('下载'),
            'audio_play' => Util::tran('语音播放'),
            'back_top' => Util::tran('回到顶部'),
            'voice_read_error' => Util::tran('朗读错误，请联网后再试'),
            'voice_not_support' => Util::tran('百度语音不支持，请使用谷歌语音接口。'),
        ]);
    }

    // 私有方法
    private function properties($page = 0)
    {
        $lang = $this->lang == 'zh-TW' ? 'zh-CN' : $this->lang;
        // 分类数据
        $data = Model::fetchAll("SELECT * FROM `category` WHERE `status` = 0 AND `lang` = '{$lang}' ORDER BY `id` ASC, `sort` DESC");
        $this->categories = array_map(function ($item) {
            $item['link'] = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . 'category/' . ($item['slug'] ? $item['slug'] : $item['id']) . ".html";
            $item['cover'] = preg_match('#(/static/[^\s"\'<>]+?\.[^\s"\'<>]+)#i', $item['cover'], $match) ? $this->site_url . $match[1] : $item['cover'];
            return $item;
        }, $data); //全部分类
        $this->categories_tree = Util::levels($this->categories); //分类树

        // 标签数据
        $data = Model::fetchAll("SELECT * FROM `tag` WHERE `status` = 0 AND `lang` = '{$lang}' ORDER BY `sort` DESC, `id` DESC");
        $this->tags = array_map(function ($item) {
            $item['link'] = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . 'tag/' . ($item['slug'] ? $item['slug'] : $item['id']) . ".html";
            return $item;
        }, $data); //全部标签

        // 热门文章
        $data = Model::fetchAll("SELECT * FROM `article` WHERE `status` = 0 AND `lang` = '{$lang}' AND `likes` > 0 ORDER BY `likes` DESC LIMIT {$this->setting['hot_num']}");
        $this->hots = array_map(function ($item) {
            $item['link'] = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . ($item['slug'] ? $item['slug'] : $item['id']) . ".html";
            return $item;
        }, $data); //全部热门文章

        // 页面数据
        $data = Model::fetchAll("SELECT * FROM `page` WHERE `status` = 0 AND `lang` = '{$lang}' ORDER BY `id` ASC, `sort` DESC");
        $this->pages = array_map(function ($item) {
            $item['link'] = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . 'page/' . ($item['slug'] ? $item['slug'] : $item['id']) . ".html";
            $item['name'] = $item['title'];
            return $item;
        }, $data); //全部页面
        $this->pages_tree = array_filter($this->pages, function ($item) {
            return $item['is_nav'] == '0';
        });
        $this->pages_tree = Util::levels($this->pages_tree); //页面树

        // 公共数据
        $this->q = isset($this->param['q']) ? stripslashes($this->param['q']) : ''; //搜索词
        $this->p = $page ? max((int)$page, 1) : 1; //分页
        $this->page_suffix = $this->p > 1 ? Util::tran('页码') . "{$this->p}" : '';
        $this->start = ($this->p - 1) * $this->limit; //第n页开始数

        // 布局数据
        $this->header = [
            'title' => [$this->setting['webName']], //SEO标题
            'description' => [], //SEO描述
            'og_tags' => [
                'og:type' => 'website',
                'og:title' => $this->setting['webName'],
                'og:description' => '',
                'og:url' => $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/'),
                'og:locale' => preg_replace('/-/', '_', $this->lang),
                'og:site_name' => $this->setting['webName'],
                'og:image' => $this->setting['webLogo'],
            ], // OG标签
            'nav' => $this->categories_tree, //导航
            'page_nav' => $this->pages_tree, //页面导航
            'crumbs' => [['name' => Util::tran('首页'), 'link' => $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/')]], //面包屑导航
        ]; //头部
        $this->main = []; //主体
        $this->aside = [
            'categories' => ['list' => $this->categories, 'title' => Util::tran('分类目录')],
            'tags' => ['list' => $this->tags, 'title' => Util::tran('标签')],
            'pages' => ['list' => $this->pages, 'title' => Util::tran('页面')],
            'hot' => ['list' => $this->hots, 'title' => Util::tran('热门文章')],
            'category_id' => $this->category_id,
            'tag_id' => $this->tag_id,
            'article_id' => $this->article_id,
            'page_id' => $this->page_id,
        ]; //侧栏
        $this->footer = []; //底部
    }

    // 系统设置
    private function setting($field = '')
    {
        static $setting;
        $default = [
            'webLogo' => '/static/images/brand.png',
            'webName' => 'Brand',
            'seo_title' => '',
            'seo_description' => '',
            'upload_type' => 'gif|jpg|jpeg|png|mp3|mp4|zip',
            'upload_size' => '2048',
            'robot_index' => '0',
            'alone_login' => '0',
            'copyright' => '&copy; 2021-' . date('Y') . ' Company, Inc.',
            'icp' => 'ICP No.001',
            'cdn_speed' => '0',
            'slide_image_1' => '',
            'slide_url_1' => '',
            'slide_image_2' => '',
            'slide_url_2' => '',
            'slide_image_3' => '',
            'slide_url_3' => '',
            'slide_image_4' => '',
            'slide_url_4' => '',
            'slide_image_5' => '',
            'slide_url_5' => '',
            'hot_num' => '5',
            'comm_num' => '5',
            'related_num' => '3',
            'page_num' => '24',
            'link_num' => 8,
            'voice_read' => '1',
            'can_comment' => '1',
            'pending_comment' => '1',
            'service' => '',
            'email' => '',
            'code' => '',
            'static' => '',
            'date' => '',
            'site' => ''
        ];
        if ($field == 'default')
            return $default;
        if ($setting === null) {
            $setting = Model::fetch('SELECT * FROM `setting` WHERE id=:id', [':id' => 1]);
            if ($setting) {
                $setting = json_decode($setting['value'], true);
                $setting = array_intersect_key($setting, $default);
                $setting = array_merge($default, $setting);
            } else {
                $setting = $default;
            }
            foreach ($setting as $key => $value) {
                if (!empty($value) && substr($value, 0, 1) == '/' && preg_match('#(/static/[^\.]+\.(gif|jpe?g|png|bmp|webp|psd|svg|tiff))#i', $value, $match)) {
                    if (isset($setting['static']) && $setting['static'] && preg_match('#/(images|upload)/#i', $value)) {
                        $cound = isset($setting['date']) && $setting['date'] && isset($setting['site']) && $setting['site'] && is_file(IA_ROOT . $match[1]) && filemtime(IA_ROOT . $match[1]) > strtotime($setting['date']);
                        $setting[$key] = ($cound ? $setting['site'] : $setting['static']) . $match[1];
                    } else {
                        $setting[$key] = $this->site_url . $match[1];
                    }
                }
            }
            $setting['webName'] = Util::tran($setting['webName']);
            $setting['seo_title'] = Util::tran($setting['seo_title']);
            $setting['seo_description'] = Util::tran($setting['seo_description']);
            $setting['copyright'] = Util::tran($setting['copyright']);
            $setting['icp'] = Util::tran($setting['icp']);
        }
        if ($field) {
            return $setting[$field];
        }
        return $setting;
    }

    // 分页数据
    private function pager($count, $p = 0, $base = '', $query = [], $is_home = false, $anchor = '')
    {
        $total = ceil($count / $this->limit);
        $list = [];
        $prev = max($p - 1, 1);
        $next = min($p + 1, max($total, 1));
        if ($p <= 2) {
            $begin = 1;
            $end = min($total, 5);
        } else {
            $end = min($p + 2, $total);
            $begin = max($end - 4, 1);
        }
        $list[] = ['title' => Util::tran("上一页"), 'p' => $prev, 'link' => $this->site_url . ($this->lang == 'zh-CN' ? '' : "/{$this->lang}") . "/{$base}" . ($prev == 1 ? "" : (($base ? "/" : "") . "page-{$prev}"))];
        for ($i = $begin; $i <= $end; $i++) {
            $list[] = ['title' => Util::tran("页码") . "{$i}", 'p' => $i, 'link' => $this->site_url . ($this->lang == 'zh-CN' ? '' : "/{$this->lang}") . "/{$base}" . ($i == 1 ? "" : (($base ? "/" : "") . "page-{$i}"))];
        }
        $list[] = ['title' => Util::tran("下一页"), 'p' => $next, 'link' => $this->site_url . ($this->lang == 'zh-CN' ? '' : "/{$this->lang}") . "/{$base}" . ($next == 1 ? "" : (($base ? "/" : "") . "page-{$next}"))];
        $list = array_map(function ($item) use ($query, $is_home, $anchor) {
            $item['link'] .= ($item['p'] == 1 && $is_home ? "" : ".html") . ($query ? "?" . implode("&", $query) : "") . $anchor;
            return $item;
        }, $list);
        return $list;
    }

    // 模板赋值
    private function vars()
    {
        $vars = get_class_vars(__CLASS__);
        array_walk($vars, function (&$value, $key) {
            $value = $this->$key;
        });
        return $vars;
    }

    // css|js压缩
    private function compress()
    {
        if (!file_exists(IA_ROOT . '/static/upload/style.css') || filemtime(IA_ROOT . '/static/upload/style.css') < filemtime(IA_ROOT . '/static/css/custom.css')) {
            $res = [];
            foreach ($this->css as $src) {
                if (is_file(IA_ROOT . '/static/' . $src)) {
                    $content = file_get_contents(IA_ROOT . '/static/' . $src);
                    if (preg_match('#^res/bootstrap#', $src)) {
                        $content = str_replace('../fonts', '../res/bootstrap/fonts', $content);
                    }
                    $res[] = trim($content);
                }
            }
            $res && file_put_contents(IA_ROOT . '/static/upload/style.css', $this->compressCss(join('', $res))) && Util::delDir(IA_ROOT . '/mvc/tpl/', true);
        }
        if (!file_exists(IA_ROOT . '/static/upload/script.js') || filemtime(IA_ROOT . '/static/upload/script.js') < filemtime(IA_ROOT . '/static/js/custom.js')) {
            $res = [];
            foreach ($this->js as $src) {
                if (is_file(IA_ROOT . '/static/' . $src)) {
                    $content = file_get_contents(IA_ROOT . '/static/' . $src);
                    $res[] = trim(trim($content, ';'));
                }
            }
            $res && file_put_contents(IA_ROOT . '/static/upload/script.js', $this->compressJs(join(';', $res))) && Util::delDir(IA_ROOT . '/mvc/tpl/', true);
        }
        if (is_file(IA_ROOT . '/static/upload/style.css')) {
            if (isset($this->setting['static']) && $this->setting['static']) {
                $cound = isset($this->setting['date']) && $this->setting['date'] && isset($this->setting['site']) && $this->setting['site'] && is_file(IA_ROOT . '/static/upload/style.css') && filemtime(IA_ROOT . '/static/upload/style.css') > strtotime($this->setting['date']);
                $this->css_url = ($cound ? $this->setting['site'] : $this->setting['static']) . '/static/upload/style.css?t=' . filemtime(IA_ROOT . '/static/upload/style.css');
            } else {
                $this->css_url = $this->site_url . '/static/upload/style.css?t=' . filemtime(IA_ROOT . '/static/upload/style.css');
            }
        }
        if (is_file(IA_ROOT . '/static/upload/script.js')) {
            if (isset($this->setting['static']) && $this->setting['static']) {
                $cound = isset($this->setting['date']) && $this->setting['date'] && isset($this->setting['site']) && $this->setting['site'] && is_file(IA_ROOT . '/static/upload/script.js') && filemtime(IA_ROOT . '/static/upload/script.js') > strtotime($this->setting['date']);
                $this->js_url = ($cound ? $this->setting['site'] : $this->setting['static']) . '/static/upload/script.js?t=' . filemtime(IA_ROOT . '/static/upload/script.js');
            } else {
                $this->js_url = $this->site_url . '/static/upload/script.js?t=' . filemtime(IA_ROOT . '/static/upload/script.js');
            }
        }
    }

    // 压缩css
    private function compressCss($css)
    {
        require_once IA_ROOT . '/util/CSSmin.php';
        return trim((new \CSSmin())->run($css));
    }

    // 压缩js
    private function compressJS($js)
    {
        require_once IA_ROOT . '/util/JSMin.php';
        return trim(\JSMin::minify($js));
    }

    // 文章数据
    private function data($data)
    {
        $siteHost = parse_url($this->site_url, PHP_URL_HOST);
        return array_map(function ($item) use ($siteHost) {
            // 生成链接
            $item['link'] = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . (isset($item['is_full']) ? 'page/' : '') . (empty($item['slug']) ? $item['id'] : $item['slug']) . '.html';
            if (!isset($item['content'])) return $item;
            // 统一处理所有匹配的标签
            preg_match_all('#<(a|img)\s+([^>]*)>#i', $item['content'], $matches, PREG_SET_ORDER);
            $pImg = $pA = 0;
            $replacements = array();
            foreach ($matches as $match) {
                $fullTag = $match[0];
                $tagType = strtolower($match[1]);
                $attrs = $match[2];
                $modified = false;
                $isEmoticon = strpos($attrs, '/emoticons/') !== false;
                // 处理图片
                if ($tagType === 'img') {
                    $newAttrs = $attrs;
                    if (!$isEmoticon) {
                        $pImg++;
                        $altText = $item['title'] . ($pImg > 1 ? "-" . Util::tran('图片') . $pImg : "");
                        // 确保alt属性处理
                        foreach (array('alt', 'title') as $attr) {
                            // 处理空属性（alt="" 或 alt=''）
                            if (preg_match("#\s{$attr}=([\"'])\\s*\\1#", $newAttrs, $m)) {
                                $newAttrs = str_replace($m[0], " {$attr}=\"{$altText}\"", $newAttrs);
                                $modified = true;
                            }
                            // 添加缺失属性
                            elseif (!preg_match("#\s{$attr}=#", $newAttrs)) {
                                $newAttrs = "{$attr}=\"{$altText}\" " . $newAttrs;
                                $modified = true;
                            }
                        }
                        // 处理class
                        if (preg_match('#class=(["\'])(.*?)\\1#', $newAttrs, $class)) {
                            if (strpos($class[2], 'img-thumbnail') === false) {
                                $newAttrs = str_replace($class[0], 'class="' . trim($class[2] . ' img-thumbnail') . '"', $newAttrs);
                                $modified = true;
                            }
                        } else {
                            $newAttrs = 'class="img-thumbnail" ' . $newAttrs;
                            $modified = true;
                        }
                    }
                    // 删除尺寸属性
                    $newAttrs = preg_replace('#\s+(width|height|align)=["\'][^"\']*["\']#i', '', $newAttrs, -1, $count);
                    $modified = $modified || ($count > 0);
                    // 处理样式
                    if (preg_match('#style=(["\'])(.*?)\\1#', $newAttrs, $style)) {
                        if (strpos($style[2], 'max-width:') === false) {
                            $newStyle = rtrim($style[2], '; ') . ';max-width:100%';
                            $newAttrs = str_replace($style[0], 'style="' . $newStyle . '"', $newAttrs);
                            $modified = true;
                        }
                    } else {
                        $newAttrs = 'style="max-width:100%" ' . $newAttrs;
                        $modified = true;
                    }
                    // 路径处理（提前处理路径）
                    if (preg_match('#src=(["\'])(?!//)([^"\']+)\\1#', $newAttrs, $src) && strpos($src[2], '/static/') !== false && stripos($src[2], $siteHost) === false) {
                        $absolute = $this->site_url . strstr($src[2], '/static/');
                        if (isset($this->setting['static']) && $this->setting['static']) {
                            $cound = isset($this->setting['date']) && $this->setting['date'] && isset($this->setting['site']) && $this->setting['site'] && is_file(IA_ROOT . strstr($src[2], '/static/')) && filemtime(IA_ROOT . strstr($src[2], '/static/')) > strtotime($this->setting['date']);
                            $absolute = ($cound ? $this->setting['site'] : $this->setting['static']) . strstr($src[2], '/static/');
                        }
                        $newAttrs = str_replace($src[0], "src={$src[1]}{$absolute}{$src[1]}", $newAttrs);
                        $modified = true;
                    }
                    // 处理封面
                    if (!isset($item['cover']) || !$item['cover']) {
                        $item['cover'] = preg_match('#src=(["\'])([^"\']+/static/[^"\']+)\\1#', $newAttrs, $src) ? $src[2] : '';
                    } else if (strpos($item['cover'], '/static/') !== false) {
                        if (isset($this->setting['static']) && $this->setting['static']) {
                            $cound = isset($this->setting['date']) && $this->setting['date'] && isset($this->setting['site']) && $this->setting['site'] && is_file(IA_ROOT . strstr($item['cover'], '/static/')) && filemtime(IA_ROOT . strstr($item['cover'], '/static/')) > strtotime($this->setting['date']);
                            $item['cover'] = ($cound ? $this->setting['site'] : $this->setting['static']) . strstr($item['cover'], '/static/');
                        }
                    }
                }
                // 处理链接
                if ($tagType === 'a') {
                    $pA++;
                    $linkTitle = $item['title'] . ($pA > 1 ? "-" . Util::tran('个数') . $pA : "");
                    if (preg_match("#title=([\"'])\\s*\\1#", $attrs, $m)) {
                        $newAttrs = str_replace($m[0], " title=\"{$linkTitle}\"", $attrs);
                        $modified = true;
                    } elseif (!preg_match("#title=#", $attrs)) {
                        $newAttrs = "title=\"{$linkTitle}\" " . $attrs;
                        $modified = true;
                    }
                }
                // 统一路径处理
                foreach (array('src', 'href') as $attr) {
                    if (preg_match("#{$attr}=([\"'])(?!//)([^\"']+)\\1#i", $newAttrs, $m)) {
                        // 只处理包含static的路径
                        if (strpos($m[2], '/static/') !== false && stripos($m[2], $siteHost) === false) {
                            $fullPath = $this->site_url . strstr($m[2], '/static/');
                            if (isset($this->setting['static']) && $this->setting['static'] && !$isEmoticon) {
                                $cound = isset($this->setting['date']) && $this->setting['date'] && isset($this->setting['site']) && $this->setting['site'] && is_file(IA_ROOT . strstr($m[2], '/static/')) && filemtime(IA_ROOT . strstr($m[2], '/static/')) > strtotime($this->setting['date']);
                                $fullPath = ($cound ? $this->setting['site'] : $this->setting['static']) . strstr($m[2], '/static/');
                            }
                            $newAttrs = str_replace($m[0], "{$attr}={$m[1]}{$fullPath}{$m[1]}", $newAttrs);
                            $modified = true;
                        }
                    }
                }
                // 懒加载处理（最后执行）
                if ($tagType === 'img' && $this->cdn_speed == '1') {
                    if (preg_match('#src=(["\'])([^"\']+/static/[^"\']+)\\1#', $newAttrs, $src)) {
                        $newAttrs = 'src="" ' . preg_replace('#src=#', 'data-src=', $newAttrs, 1);
                        $modified = true;
                    }
                }
                if ($modified) {
                    $replacements[$fullTag] = "<{$tagType} {$newAttrs}>";
                }
            }
            // 批量替换内容
            if (!empty($replacements)) {
                $item['content'] = strtr($item['content'], $replacements);
            }
            return $item;
        }, $data);
    }

    // 访问类中不存在的方法时抛出404
    public function __call($name, $arguments)
    {
        View::display('404.html');
    }

    // 首页
    public function index($page = 0)
    {
        $this->properties($page);
        $lang = $this->lang == 'zh-TW' ? 'zh-CN' : $this->lang;

        // 头部
        if ($this->setting['seo_title']) array_unshift($this->header['title'], $this->setting['seo_title']);
        if ($this->setting['seo_description']) array_unshift($this->header['description'], $this->setting['seo_description']);
        $this->header['og_tags']['og:type'] = 'website';
        $this->header['og_tags']['og:title'] = implode('-', $this->header['title']);
        $this->header['og_tags']['og:description'] = implode('-', $this->header['description']);
        array_push($this->header['crumbs'], ['name' => $this->setting['webName']]);

        // 主体
        $count = Model::fetchColumn("SELECT COUNT(*) FROM `article` WHERE `status` = 0 AND `lang` = '{$lang}' ORDER BY `id` DESC");
        $data = Model::fetchAll("SELECT * FROM `article` WHERE `status` = 0 AND `lang` = '{$lang}' ORDER BY `id` DESC LIMIT $this->start, $this->limit");
        $this->main = array_merge(
            $this->main,
            ['title' => Util::tran('最新文章') . ($this->page_suffix ? "({$this->page_suffix})" : '')],
            ['list' => $this->data($data)],
            ['count' => $count],
            ['pager' => $this->pager($count, $this->p, '', [], true)]
        );

        // 侧栏
        $data = Model::fetchAll("SELECT * FROM `comment` WHERE `pid` = 0 AND `status` = 0 AND `lang` = '{$lang}' ORDER BY `sort` DESC, `id` DESC LIMIT {$this->setting['comm_num']}");
        $authors = [];
        $aids = [];
        foreach ($data as $item) {
            $authors[] = $item['author'];
            $aids[] = $item['article_id'];
        }
        $authors = array_unique($authors);
        $authors = implode("','", $authors);
        $users = Model::fetchAll("SELECT * FROM `user` WHERE `username` IN ('$authors')");
        $aids = array_unique($aids);
        $aids = implode(',', $aids);
        if ($aids) {
            $articles = Model::fetchAll("SELECT * FROM `article` WHERE `status` = 0 AND `id` IN ($aids) ORDER BY `id`");
        } else {
            $articles = [];
        }
        $data = array_map(function ($item) use ($users, $articles) {
            // 评论图像
            $item['avatar'] = '';
            if ($user = array_filter($users, function ($elem) use ($item) {
                return $elem['username'] == $item['author'];
            })) {
                $user = array_values($user);
                $item['nickname'] = $user[0]['nickname'] ?: $user[0]['username'];
                $item['avatar'] = $user[0]['avatar'] ? (preg_match('#(/static/upload/[^\s"\'<>]+?\.[^\s"\'<>]+)#i', $user[0]['avatar'], $match) ? $this->site_url . $match[1] : $user[0]['avatar']) : '';
            }
            // 被评论的文章
            $item['article_title'] = $item['author'];
            $item['article_link'] = '#';
            if ($article = array_filter($articles, function ($elem) use ($item) {
                return $elem['id'] == $item['article_id'];
            })) {
                $article = array_values($article);
                $item['article_title'] = $article[0]['title'];
                $item['article_link'] = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . ($article[0]['slug'] ? $article[0]['slug'] : $article[0]['id']) . '.html';
            }
            return $item;
        }, $data);
        $this->aside['comment'] = ['list' => $data, 'title' => Util::tran('最新评论')];

        // 底部
        $data = Model::fetchAll("SELECT * FROM `link` WHERE `status` = 0 ORDER BY `id` ASC, `sort` DESC LIMIT {$this->setting['link_num']}");
        $this->footer = array_merge($this->footer, ['link' => $data]);
        View::assign($this->vars());
        View::display('index/index.html');
    }

    // 分类页
    public function category($id_or_slug, $page = 0)
    {
        $this->properties($page);
        $rows = array_filter($this->categories, function ($item) use ($id_or_slug) {
            return is_numeric($id_or_slug) ? $item['id'] == $id_or_slug : $item['slug'] == $id_or_slug;
        });
        $rows = array_values($rows);
        $cid = $rows ? $rows[0]['id'] : 0;
        $this->category = $rows ? $rows[0] : [];
        $this->category_id = $rows ? $rows[0]['id'] : 0;

        // 相册分页
        if ($this->category && $this->category['is_gallery']) {
            $this->limit = $this->setting['page_num'];
            $this->start = ($this->p - 1) * $this->limit; //第n页开始数
        }

        // 头部
        $this->page_suffix && array_unshift($this->header['title'], $this->page_suffix);
        array_unshift($this->header['title'], $rows ? ($rows[0]['seo_title'] ? $rows[0]['seo_title'] : $rows[0]['name']) : Util::tran('未分类'));
        array_unshift($this->header['description'], $rows ? ($rows[0]['seo_description'] ? strip_tags($rows[0]['seo_description']) : strip_tags($rows[0]['description'])) : '');
        $this->page_suffix && array_unshift($this->header['description'], $this->page_suffix);
        $this->header['og_tags']['og:type'] = 'website';
        $this->header['og_tags']['og:title'] = implode('-', $this->header['title']);
        $this->header['og_tags']['og:description'] = implode('-', $this->header['description']);
        $this->header['og_tags']['og:url'] = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . 'category/' . ($this->p == 1 ? "{$id_or_slug}.html" : "{$id_or_slug}/page-{$this->p}.html");
        if (isset($this->category['cover']) && is_file(IA_ROOT . (string)strstr($this->category['cover'], '/static/'))) {
            list($width, $height) = getimagesize(IA_ROOT . (string)strstr($this->category['cover'], '/static/'));
            $this->header['og_tags']['og:image'] = $this->category['cover'];
            $this->header['og_tags']['og:image:width'] = $width;
            $this->header['og_tags']['og:image:height'] = $height;
            $this->header['og_tags']['og:image:alt'] = strip_tags($this->category['name']);
        }
        $parents = Util::parents($this->categories, $cid);
        $parents = array_reverse($parents);
        $ids = [];
        foreach ($parents as $item) {
            $ids[] = $item['id'];
            if (count($parents) == count($ids))
                continue;
            array_push($this->header['crumbs'], ['link' => $item['link'], 'name' => $item['name']]);
        }
        array_push($this->header['crumbs'], ['name' => $rows ? $rows[0]['name'] : Util::tran('未分类')]);

        // 主体
        $childs = Util::trees($this->categories, $cid);
        $cids = array_map(function ($item) {
            return $item['id'];
        }, $childs);
        array_unshift($cids, $cid);
        $cidStr = implode(',', $cids);
        $count = Model::fetchColumn("SELECT COUNT(*) FROM `article` WHERE `status` = 0 AND `category_id` IN ($cidStr) ORDER BY `id` DESC");
        $data = Model::fetchAll("SELECT * FROM `article` WHERE `status` = 0 AND `category_id` IN ($cidStr) ORDER BY `id` DESC LIMIT $this->start, $this->limit");
        $this->main = array_merge(
            $this->main,
            ['title' => ($rows ? $rows[0]['name'] : Util::tran('未分类')) . ($this->page_suffix ? "({$this->page_suffix})" : '')],
            ['list' => $this->data($data)],
            ['count' => $count],
            ['pager' => $this->pager($count, $this->p, "category/{$id_or_slug}")]
        );

        // 侧栏
        $data = Model::fetchAll("SELECT `id`, `title`, `slug`, `likes`, `update_at` FROM `article` WHERE `status` = 0 AND `category_id` IN ($cidStr) ORDER BY `id` DESC");
        $this->aside['self'] = ['list' => $this->data($data), 'title' => $rows ? $rows[0]['name'] : Util::tran('未分类')];

        View::assign($this->vars());
        View::assign('tran', [
            'uncategoried' => Util::tran('未分类'),
            'articles' => Util::tran('篇文章'),
            'no_description' => Util::tran('还没有描述信息'),
        ]);
        if ($this->category && $this->category['is_gallery']) {
            // 相册
            View::display('index/gallery.html');
        } elseif ($this->category) {
            // 列表
            View::display('index/category.html');
        } else {
            // 404
            $this->is_404 = true;
            View::display('404.html');
        }
    }

    // 标签页
    public function tag($id_or_slug, $page = 0)
    {
        $this->properties($page);
        $rows = array_filter($this->tags, function ($item) use ($id_or_slug) {
            return is_numeric($id_or_slug) ? $item['id'] == $id_or_slug : $item['slug'] == $id_or_slug;
        });
        $rows = array_values($rows);
        $tid = $rows ? $rows[0]['id'] : '';
        $this->tag = $rows ? $rows[0] : [];
        $this->tag_id = $rows ? $rows[0]['id'] : 0;

        // 头部
        $this->page_suffix && array_unshift($this->header['title'], $this->page_suffix);
        array_unshift($this->header['title'], $rows ? ($rows[0]['seo_title'] ? $rows[0]['seo_title'] : $rows[0]['name']) : Util::tran('空标签'));
        array_unshift($this->header['description'], $rows ? ($rows[0]['seo_description'] ? strip_tags($rows[0]['seo_description']) : strip_tags($rows[0]['description'])) : '');
        $this->page_suffix && array_unshift($this->header['description'], $this->page_suffix);
        $this->header['og_tags']['og:type'] = 'website';
        $this->header['og_tags']['og:title'] = implode('-', $this->header['title']);
        $this->header['og_tags']['og:description'] = implode('-', $this->header['description']);
        $this->header['og_tags']['og:url'] = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . 'tag/' . ($this->p == 1 ? "{$id_or_slug}.html" : "{$id_or_slug}/page-{$this->p}.html");
        array_push($this->header['crumbs'], ['name' => $rows ? $rows[0]['name'] : Util::tran('空标签')]);

        // 主体
        $count = Model::fetchColumn("SELECT COUNT(*) FROM `article` WHERE `status` = 0 AND `tag_ids` LIKE '%\"{$tid}\"%' ORDER BY `id` DESC");
        $data = Model::fetchAll("SELECT * FROM `article` WHERE `status` = 0 AND `tag_ids` LIKE '%\"{$tid}\"%' ORDER BY `id` DESC LIMIT $this->start, $this->limit");
        $this->main = array_merge(
            $this->main,
            ['title' => ($rows ? $rows[0]['name'] : Util::tran('空标签')) . ($this->page_suffix ? "({$this->page_suffix})" : '')],
            ['list' => $this->data($data)],
            ['count' => $count],
            ['pager' => $this->pager($count, $this->p, "tag/{$id_or_slug}")]
        );

        View::assign($this->vars());
        if ($this->tag) {
            View::display('index/tag.html');
        } else {
            // 404
            $this->is_404 = true;
            View::display('404.html');
        }
    }

    // 搜索页
    public function search($page = 0)
    {
        $this->properties($page);
        $lang = $this->lang == 'zh-TW' ? 'zh-CN' : $this->lang;

        // 头部
        $this->page_suffix && array_unshift($this->header['title'], $this->page_suffix);
        array_unshift($this->header['title'], Util::tran('搜索结果'));
        array_unshift($this->header['description'], Util::tran('搜索结果') . $this->q);
        $this->page_suffix && array_unshift($this->header['description'], $this->page_suffix);
        $this->header['og_tags']['og:type'] = 'website';
        $this->header['og_tags']['og:title'] = implode('-', $this->header['title']);
        $this->header['og_tags']['og:description'] = implode('-', $this->header['description']);
        $this->header['og_tags']['og:url'] = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . ($this->p == 1 ? "/search.html?q={$this->q}" : "/search/page-{$this->p}.html?q={$this->q}");
        array_push($this->header['crumbs'], ['name' => Util::tran('搜索结果')]);

        // 主体
        $count = Model::fetchColumn("SELECT COUNT(*) FROM `article` WHERE `status` = 0 AND `lang` = '{$lang}' AND `title` LIKE '%{$this->q}%' ORDER BY `id` DESC");
        $data = Model::fetchAll("SELECT * FROM `article` WHERE `status` = 0 AND `lang` = '{$lang}' AND `title` LIKE '%{$this->q}%' ORDER BY `id` DESC LIMIT $this->start, $this->limit");
        $this->main = array_merge(
            $this->main,
            ['title' => Util::tran('搜索结果') . ($this->page_suffix ? "({$this->page_suffix})" : '')],
            ['list' => $this->data($data)],
            ['count' => $count],
            ['pager' => $this->pager($count, $this->p, 'search', ['q=' . $this->q])]
        );

        View::assign($this->vars());
        View::display('index/search.html');
    }

    // 内容页
    public function detail($id_or_slug, $page = 0)
    {
        $this->properties($page);
        $lang = $this->lang == 'zh-TW' ? 'zh-CN' : $this->lang;
        $where = is_numeric($id_or_slug) ? " AND `id` = $id_or_slug" : " AND `slug` = '$id_or_slug'";
        $row = Model::fetch("SELECT * FROM `article` WHERE `status` = 0 AND `lang` = '{$lang}' $where ORDER BY `id` LIMIT 1");
        $this->article = $row ? $this->data([$row])[0] : [];
        $this->article && ($this->article['link'] = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . $id_or_slug . '.html');
        $this->article_id = $row ? $row['id'] : 0;
        $this->article_views = $row ? $row['views'] : 0;
        $this->article_likes = $row ? $row['likes'] : 0;
        $file = '/static/upload/articles/ID' . str_pad($this->article_id, 7, '0', STR_PAD_LEFT) . '-' . str_replace('/', '-', $this->lang == 'zh-CN' ? '' : $this->lang . '/') . $id_or_slug . '.html';
        $this->download = is_file(IA_ROOT . $file) ? $this->site_url . $file : '';

        $rows = array_filter($this->categories, function ($item) use ($row) {
            return $item['id'] == ($row ? $row['category_id'] : 0);
        });
        $rows = array_values($rows);
        $cid = $rows ? $rows[0]['id'] : 0;
        $this->category = $rows ? $rows[0] : [];
        $this->category_id = $rows ? $rows[0]['id'] : 0;

        // 头部
        $this->page_suffix && array_unshift($this->header['title'], $this->page_suffix);
        array_unshift($this->header['title'], $row ? ($row['seo_title'] ? $row['seo_title'] : $row['title']) : Util::tran('无标题'));
        array_unshift($this->header['description'], $row ? ($row['seo_description'] ? strip_tags($row['seo_description']) : ($row['description'] ? strip_tags($row['description']) : mb_strimwidth(preg_replace('/\s+/sim', ' ', str_replace('"', '', strip_tags($row['content']))), 0, 140, '...', 'UTF-8'))) : '');
        $this->page_suffix && array_unshift($this->header['description'], $this->page_suffix);
        $this->header['og_tags']['og:type'] = 'article';
        $this->header['og_tags']['og:title'] = implode('-', $this->header['title']);
        $this->header['og_tags']['og:description'] = implode('-', $this->header['description']);
        $this->header['og_tags']['og:url'] = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . ($this->p == 1 ? "{$id_or_slug}.html" : "{$id_or_slug}/page-{$this->p}.html");
        if ($row && is_file(IA_ROOT . (string)strstr($this->article['cover'], '/static/'))) {
            list($width, $height) = getimagesize(IA_ROOT . (string)strstr($this->article['cover'], '/static/'));
            $this->header['og_tags']['og:image'] = $this->article['cover'];
            $this->header['og_tags']['og:image:width'] = $width;
            $this->header['og_tags']['og:image:height'] = $height;
            $this->header['og_tags']['og:image:alt'] = isset($this->article['title']) ? strip_tags($this->article['title']) : Util::tran('无标题');
        }
        $parents = Util::parents($this->categories, $cid);
        $parents = array_reverse($parents);
        $ids = [];
        $this->header['og_tags']['article:section'] = [];
        foreach ($parents as $item) {
            $ids[] = $item['id'];
            array_push($this->header['crumbs'], ['link' => $item['link'], 'name' => $item['name']]);
            $this->header['og_tags']['article:section'][] = $item['name'];
        }
        array_push($this->header['crumbs'], ['name' => $row ? $row['title'] : Util::tran('无标题')]);

        // 主体
        $this->main = array_merge(
            $this->main,
            ['article' => $this->article]
        );
        // 上一篇|下一篇
        // $prev = Model::fetch("SELECT * FROM `article` WHERE `status` = 0 AND `category_id` = :cid AND `id` < :id ORDER BY `id` DESC LIMIT 1", [':cid' => $cid, ':id' => $this->article_id]);
        $prev = Model::fetch("SELECT * FROM `article` WHERE `status` = 0 AND `id` < :id ORDER BY `id` DESC LIMIT 1", [':id' => $this->article_id]);
        $prev && ($prev['link'] = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . ($prev['slug'] ? $prev['slug'] : $prev['id']) . '.html');
        // $next = Model::fetch("SELECT * FROM `article` WHERE `status` = 0 AND `category_id` = :cid AND `id` > :id ORDER BY `id` ASC LIMIT 1", [':cid' => $cid, ':id' => $this->article_id]);
        $next = Model::fetch("SELECT * FROM `article` WHERE `status` = 0 AND `id` > :id ORDER BY `id` ASC LIMIT 1", [':id' => $this->article_id]);
        $next && ($next['link'] = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . ($next['slug'] ? $next['slug'] : $next['id']) . '.html');
        $this->main = array_merge(
            $this->main,
            ['prev' => $prev],
            ['next' => $next]
        );
        // 标签
        $tags = array_filter($this->tags, function ($item) use ($row) {
            return in_array($item['id'], $row ? (array)json_decode($row['tag_ids'], true) : []);
        });
        $this->header['og_tags']['article:tag'] = array_map(function ($item) {
            return $item['name'];
        }, $tags);
        // rich attachment
        if ($row && preg_match_all('#<(video|audio|source|embed)[^>]*src=["\'](' . preg_quote($this->site_url, '#') . '[^"\']*\.[^"\']*)["\'][^>]*>#sim', $this->article['content'], $matches)) {
            $reverse = array_reverse($matches[2]);
            foreach ($reverse as $url) {
                $ext = pathinfo($url, PATHINFO_EXTENSION);
                $type = Util::mimeType($ext);
                $this->header['og_tags']['og:rich_attachment'] = 'true';
                if (preg_match('/^video/', $type)) {
                    $this->header['og_tags']['og:video:url'] = $url;
                    $this->header['og_tags']['og:video:type'] = $type;
                } else if (preg_match('/^audio/', $type)) {
                    $this->header['og_tags']['og:audio:url'] = $url;
                    $this->header['og_tags']['og:audio:type'] = $type;
                }
            }
        }
        // twitter
        $this->header['og_tags']['twitter:card'] = 'summary';
        if (isset($this->header['og_tags']['og:image:width']) && $this->header['og_tags']['og:image:width'] > 520) {
            $this->header['og_tags']['twitter:card'] = 'summary_large_image';
        }
        foreach (['title', 'description', 'image', 'url'] as $key) {
            if (isset($this->header['og_tags']['og:' . $key])) {
                $this->header['og_tags']['twitter:' . $key] = $this->header['og_tags']['og:' . $key];
            }
        }
        $this->header['og_tags']['article:published_time'] = date('c', $row ? $this->article['create_at'] : '0');
        $this->header['og_tags']['article:modified_time'] = date('c', $row ? $this->article['update_at'] : '0');
        $this->header['og_tags']['article:author'] = $row ? $this->article['author'] : 'anonymous';
        $this->header['og_tags']['article:published_first'] = sprintf('%s,%s', $row ? $this->article['title'] : Util::tran('无标题'), $this->header['og_tags']['og:url']);
        $this->header['og_tags']['og:release_date'] = date('c', $row ? $this->article['create_at'] : '0');
        $this->header['og_tags']['og:updated_time'] = date('c', $row ? $this->article['update_at'] : '0');
        $this->header['og_tags']['profile:username'] = $row ? $this->article['author'] : 'anonymous';
        $this->main = array_merge(
            $this->main,
            ['tags' => array_values($tags)]
        );
        // 相关文章
        $tag_ids = isset($this->article['tag_ids']) && $this->article['tag_ids'] ? json_decode($this->article['tag_ids'], true) : [];
        $tag_ids = array_map('intval', $tag_ids);
        $tag_ids = array_filter($tag_ids);
        // 按标签获取
        $related = [];
        for ($i = 0; $i < count($tag_ids); $i++) {
            $related_num = $this->setting['related_num'] + 1;
            $related_articles = Model::fetchAll("SELECT * FROM `article` WHERE `status` = 0 AND `tag_ids` LIKE '%\"{$tag_ids[$i]}\"%' ORDER BY `id` DESC LIMIT $related_num");
            // 合并去重
            $related = array_merge($related, $related_articles);
            $related = array_unique($related, SORT_REGULAR);
            // 排除当前文章
            $related = array_filter($related, function ($item) {
                return $item['id'] !== $this->article_id;
            });
            // 获取设置的数量
            $related = array_slice($related, 0, $related_num - 1);
            if (count($related) >= $related_num - 1)
                break;
        }
        $this->main = array_merge($this->main, ['related' => $this->data($related)]);
        // 评论列表
        $count = Model::fetchColumn("SELECT COUNT(*) FROM `comment` WHERE `status` = 0 AND `article_id` = $this->article_id AND `pid` = 0 ORDER BY `sort` DESC, `id` DESC");
        $data = Model::fetchAll("SELECT * FROM `comment` WHERE `status` = 0 AND `article_id` = $this->article_id AND `pid` = 0 ORDER BY `sort` DESC, `id` DESC LIMIT $this->start, $this->limit");
        $replay_ids = array_map(function ($item) {
            return $item['id'];
        }, $data);
        $replay_ids = array_unique($replay_ids);
        $replay_ids = implode(',', $replay_ids);
        if ($replay_ids)
            $replies = Model::fetchAll("SELECT * FROM `comment` WHERE `status` = 0 AND `article_id` = $this->article_id AND `pid` IN ($replay_ids) ORDER BY `sort` DESC, `id` DESC");
        else
            $replies = [];
        // 获取用户图像
        $authors = array_map(function ($item) {
            return $item['author'];
        }, array_merge($data, $replies));
        $authors = array_unique($authors);
        $authors = implode("','", $authors);
        if ($authors)
            $users = Model::fetchAll("SELECT * FROM `user` WHERE `status` = 0 AND `username` IN ('$authors') ORDER BY `id`");
        else
            $users = [];
        $names = [];
        foreach ($users as $user) {
            $names[$user['username']] = [];
            $names[$user['username']]['nickname'] = $user['nickname'] ?: $user['username'];
            $names[$user['username']]['avatar'] = $user['avatar'] ? (preg_match('#(/static/upload/[^\s"\'<>]+?\.[^\s"\'<>]+)#i', $user['avatar'], $match) ? $this->site_url . $match[1] : $user['avatar']) : '';
        }
        // 组合数据
        $data = array_map(function ($item) use ($names) {
            $item['avatar'] = isset($names[$item['author']]) ? $names[$item['author']]['avatar'] : '';
            $item['nickname'] = isset($names[$item['author']]) ? $names[$item['author']]['nickname'] : '';
            if (preg_match_all('#<[^>]*=(["\'])(.*?)(/static/(res|upload)/[^\s"\'<>]+?\.[^\s"\'<>]+)\1#i', $item['comment'], $matches, PREG_SET_ORDER)) {
                foreach ($matches as $match) {
                    $item['comment'] = str_replace($match[2] . $match[3], $this->site_url . $match[3], $item['comment']);
                }
            }
            return $item;
        }, $data);
        $replies = array_map(function ($item) use ($names) {
            $item['avatar'] = isset($names[$item['author']]) ? $names[$item['author']]['avatar'] : '';
            $item['nickname'] = isset($names[$item['author']]) ? $names[$item['author']]['nickname'] : '';
            if (preg_match_all('#<[^>]*=(["\'])(.*?)(/static/(res|upload)/[^\s"\'<>]+?\.[^\s"\'<>]+)\1#i', $item['comment'], $matches, PREG_SET_ORDER)) {
                foreach ($matches as $match) {
                    $item['comment'] = str_replace($match[2] . $match[3], $this->site_url . $match[3], $item['comment']);
                }
            }
            return $item;
        }, $replies);
        $data = array_map(function ($item) use ($replies) {
            $item['replies'] = array_values(array_filter($replies, function ($elem) use ($item) {
                return $elem['pid'] == $item['id'];
            }));
            return $item;
        }, $data);
        $this->main = array_merge(
            $this->main,
            ['comments' => $data],
            ['count' => $count],
            ['pager' => $this->pager($count, $this->p, $id_or_slug, [], false, '#comments')]
        );

        // 侧栏
        $idStr = $ids ? implode(',', $ids) : $cid;
        $data = Model::fetchAll("SELECT `id`, `title`, `slug`, `likes`, `update_at` FROM `article` WHERE `status` = 0 AND `category_id` IN ($idStr) ORDER BY `id` DESC");
        $this->aside['self'] = ['list' => $this->data($data), 'title' => $rows ? $rows[0]['name'] : Util::tran('未分类')];

        View::assign($this->vars());
        View::assign('tran', [
            'untitled' => Util::tran('无标题'),
            'uncategoried' => Util::tran('未分类'),
            'custom' => Util::tran('客户端'),
            'wechat' => Util::tran('微信'),
            'no_content' => Util::tran('还没有内容信息'),
            'download_content' => Util::tran('下载内容'),
            'favorite_article' => Util::tran('收藏文章'),
            'copyr_info' => Util::tran('除非特别说明，本博客均为原创，如需转载，请以链接形式标明来源出处。'),
            'copyr_address' => Util::tran('本文博客网址'),
            'no_address' => Util::tran('还没有网址'),
            'article_useful' => Util::tran('这篇文章很有用'),
            'people_likes' => Util::tran('人觉得这篇文章很赞'),
            'nothing' => Util::tran('没有了'),
            'related_articles' => Util::tran('相关文章'),
            'post_comment' => Util::tran('发表评论'),
            'comment_content' => Util::tran('评论内容'),
            'please' => Util::tran('请'),
            'login' => Util::tran('登录'),
            'after_comment' => Util::tran('后评论'),
            'say' => Util::tran('说道'),
            'delete' => Util::tran('删除'),
            'reply' => Util::tran('回复'),
            'no_comment' => Util::tran('还没有评论'),
        ]);
        if ($this->article) {
            View::display('index/detail.html');
        } else {
            // 404
            $this->is_404 = true;
            View::display('404.html');
        }
    }

    // 独立页面
    public function page($id_or_slug, $page = 0)
    {
        $this->properties($page);
        $rows = array_filter($this->pages, function ($item) use ($id_or_slug) {
            return is_numeric($id_or_slug) ? $item['id'] == $id_or_slug && $item['status'] == 0 : $item['slug'] == $id_or_slug && $item['status'] == 0;
        });
        $rows = array_values($rows);
        $row = $rows ? $this->data([$rows[0]])[0] : [];
        $this->page = $row ? $row : [];
        $this->page_id = $row ? $row['id'] : 0;

        // 头部
        $this->page_suffix && array_unshift($this->header['title'], $this->page_suffix);
        array_unshift($this->header['title'], $row ? ($row['seo_title'] ? $row['seo_title'] : $row['title']) : Util::tran('无标题'));
        array_unshift($this->header['description'], $row ? (strip_tags($row['seo_description']) ? strip_tags($row['seo_description']) : (strip_tags($row['description']) ? strip_tags($row['description']) : mb_strimwidth(preg_replace('/\s+/sim', ' ', str_replace('"', '', strip_tags($row['content']))), 0, 140, '...', 'UTF-8'))) : '');
        $this->page_suffix && array_unshift($this->header['description'], $this->page_suffix);
        $this->header['og_tags']['og:type'] = 'article';
        $this->header['og_tags']['og:title'] = implode('-', $this->header['title']);
        $this->header['og_tags']['og:description'] = implode('-', $this->header['description']);
        $this->header['og_tags']['og:url'] = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . ($this->p == 1 ? "page/{$id_or_slug}.html" : "/page/{$id_or_slug}/page-{$this->p}.html");
        // rich attachment
        if ($row && preg_match_all('#<(video|audio|source|embed|img)[^>]*src=["\'](' . preg_quote($this->site_url, '#') . '[^"\']*\.[^"\']*)["\'][^>]*>#sim', $this->page['content'], $matches)) {
            $reverse = array_reverse($matches[2]);
            foreach ($reverse as $url) {
                $ext = pathinfo($url, PATHINFO_EXTENSION);
                $type = Util::mimeType($ext);
                if (preg_match('/^image/', $type)) {
                    list($width, $height) = getimagesize(str_replace($this->site_url, IA_ROOT, $url));
                    $this->header['og_tags']['og:image'] = $url;
                    $this->header['og_tags']['og:image:width'] = $width;
                    $this->header['og_tags']['og:image:height'] = $height;
                    $this->header['og_tags']['og:image:alt'] = strip_tags($this->page['title']);
                } else if (preg_match('/^video/', $type)) {
                    $this->header['og_tags']['og:rich_attachment'] = 'true';
                    $this->header['og_tags']['og:video:url'] = $url;
                    $this->header['og_tags']['og:video:type'] = $type;
                } else if (preg_match('/^audio/', $type)) {
                    $this->header['og_tags']['og:rich_attachment'] = 'true';
                    $this->header['og_tags']['og:audio:url'] = $url;
                    $this->header['og_tags']['og:audio:type'] = $type;
                }
            }
        }
        // twitter
        $this->header['og_tags']['twitter:card'] = 'summary';
        if (isset($this->header['og_tags']['og:image:width']) && $this->header['og_tags']['og:image:width'] > 520) {
            $this->header['og_tags']['twitter:card'] = 'summary_large_image';
        }
        foreach (['title', 'description', 'image', 'url'] as $key) {
            if (isset($this->header['og_tags']['og:' . $key])) {
                $this->header['og_tags']['twitter:' . $key] = $this->header['og_tags']['og:' . $key];
            }
        }
        $this->header['og_tags']['article:published_time'] = date('c', $row ? $this->page['time'] : '0');
        $this->header['og_tags']['article:published_first'] = sprintf('%s,%s', $row ? $this->page['title'] : Util::tran('无标题'), $this->header['og_tags']['og:url']);
        $this->header['og_tags']['og:release_date'] = date('c', $row ? $this->page['time'] : '0');
        $parents = Util::parents($this->pages, $this->page_id);
        $parents = array_reverse($parents);
        $ids = [];
        foreach ($parents as $item) {
            $ids[] = $item['id'];
            if (count($parents) == count($ids))
                continue;
            array_push($this->header['crumbs'], ['link' => $item['link'], 'name' => $item['title']]);
        }
        array_push($this->header['crumbs'], ['name' => $rows ? $rows[0]['name'] : Util::tran('无标题')]);

        // 主体
        $children = array_filter($this->pages, function ($item) {
            return $item['pid'] == $this->page_id;
        });
        $children = array_values($children);
        $this->main = array_merge(
            $this->main,
            ['count' => count($children)],
            ['list' => $this->data(array_slice($children, $this->start, $this->limit))],
            ['pager' => $this->pager(count($children), $this->p, "page/{$id_or_slug}")]
        );

        View::assign($this->vars());
        View::assign('tran', [
            'unnamed' => Util::tran('未命名'),
            'no_page' => Util::tran('还没有页面'),
            'articles' => Util::tran('篇文章'),
            'no_description' => Util::tran('还没有描述信息'),
        ]);
        if ($this->page && $this->page['slug'] && is_file(IA_ROOT . '/view/index/' . $this->page['slug'] . '.html')) {
            View::display('index/' . $this->page['slug'] . '.html');
        } elseif ($this->page && $this->page['is_full']) {
            View::display('index/page-full.html');
        } elseif ($this->page) {
            View::display('index/page.html');
        } else {
            // 404
            $this->is_404 = true;
            View::display('404.html');
        }
    }

    // Ajax处理
    public function ajax($action)
    {
        switch ($action) {
                // ajax点击数|点赞数
            case 'views':
            case 'likes':
                $param = Util::param(['id', $action]);
                if (isset($param[$action])) {
                    $$action = (int)$param[$action];
                    if (isset($param['id']) && $param['id']) {
                        Model::update('article', "$action = $action + 1", ['id' => $param['id']]);
                        $$action += 1;
                    }
                    Util::json(['code' => 0, 'msg' => $$action], $this->callback);
                }
                break;

                // 登录检查
            case 'islogin':
                $this->login && Util::json(['code' => 1, 'msg' => Util::tran('你已经登录')], $this->callback);
                Util::json(['code' => 0, 'msg' => Util::tran('请先登录')], $this->callback);
                break;

                // 登入
            case 'login':
                $this->login && Util::json(['code' => 1, 'msg' => Util::tran('你已经登录')], $this->callback);
                $ip = Util::getIp();
                $param = Util::param(['username', 'password', 'remember']);
                empty($param['username']) && Util::json(['code' => 1, 'msg' => Util::tran('请输入用户名或邮箱')], $this->callback);
                $check_type = false === strpos($param['username'], '@') ? 'username' : 'email';
                if (($check_type == 'username' && !preg_match('#^[a-z0-9_-]{3,12}$#', $param['username'])) || ($check_type == 'email' && !filter_var($param['username'], FILTER_VALIDATE_EMAIL)))
                    Util::json(['code' => 1, 'msg' => Util::tran('用户名或邮箱格式错误') . ', ' . Util::tran('用户名只能英文字母数字下划线或中划线，位数') . ' 3~12'], $this->callback);
                if (empty($param['password']) || strlen($param['password']) < 5)
                    Util::json(['code' => 1, 'msg' => Util::tran('密码至少位数') . ' 5'], $this->callback);
                if (!isset($param['remember']) || !in_array((int)$param['remember'], [0, 1]))
                    $param['remember'] = 1;

                // 数据库查询
                $user = Model::fetch("SELECT * FROM `user` WHERE `$check_type`=:username AND `password`=:password ORDER BY `id` ASC LIMIT 1", [
                    'username' => $param['username'],
                    'password' => md5(base64_encode($param['password']))
                ]);
                if (!$user) {
                    Util::config('log') && Model::insert('log', ['author' => $param['username'], 'request_method' => 'ajax', 'url' => htmlentities($_SERVER['REQUEST_URI']), 'title' => Util::tran('前端登录'), 'message' => Util::tran('用户名或密码错误'), 'ip' => $ip, 'status' => 1, 'time' => time()]);
                    Util::json(['code' => 1, 'msg' => Util::tran('用户名或密码错误')], $this->callback);
                }
                if ('1' == $user['status']) {
                    Util::config('log') && Model::insert('log', ['author' => $param['username'], 'request_method' => 'ajax', 'url' => htmlentities($_SERVER['REQUEST_URI']), 'title' => Util::tran('前端登录'), 'message' => Util::tran('账号被禁用'), 'ip' => $ip, 'status' => 1, 'time' => time()]);
                    Util::json(['code' => 1, 'msg' => Util::tran('你的账号已被禁用，请联系管理员')], $this->callback);
                }

                // 登录成功
                $_SESSION['uid'] = $user['id'];

                // 记住登录
                $token = md5($user['username'] . md5(base64_encode($user['password'])));
                if ('1' == $param['remember']) {
                    setcookie('token', $token, time() + 3600 * 24 * 30, '/');
                    Model::update('user', ['remember_token' => $token], ['id' => $user['id']]);
                } else {
                    Model::update('user', ['remember_token' => ''], ['id' => $user['id']]);
                }

                // 单点登录
                $setting = Model::fetch('SELECT * FROM `setting` WHERE id=:id ORDER BY `id` ASC LIMIT 1', [':id' => 1]);
                if ($setting && ($check = json_decode($setting['value'], true)) && isset($check['alone_login']) && $check['alone_login']) {
                    $randstr = md5($token . rand(100000, 999999));
                    Util::setCache('rand' . $user['id'], $randstr);
                    setcookie('randstr', $randstr, time() + 3600 * 24 * 30, '/');
                }

                // 更新时间
                !$user['first_login'] && Model::update('user', ['first_login' => time()], ['id' => $user['id']]);
                Model::update('user', ['last_login' => time()], ['id' => $user['id']]);

                // 记录日志
                Util::config('log') && Model::insert('log', ['author' => $user['username'], 'request_method' => 'ajax', 'url' => htmlentities($_SERVER['REQUEST_URI']), 'title' => Util::tran('前端登录'), 'message' => Util::tran('登录成功'), 'ip' => $ip, 'status' => 0, 'time' => time()]);

                // 清除来源页缓存
                if (isset($_SERVER["HTTP_REFERER"])) {
                    $file_name = md5($_SERVER["HTTP_REFERER"]);
                    if ($pos = strpos($_SERVER["HTTP_REFERER"], 'redirect_to=')) {
                        $file_name = md5(substr($_SERVER["HTTP_REFERER"], $pos + 12));
                    }
                    $cache_file = IA_ROOT . '/mvc/tpl/' . substr($file_name, 0, 2) . '/' . $file_name . '.php';
                    is_file($cache_file) && unlink($cache_file);
                }

                // 清除来源页缓存
                $this->clearOrgin();

                // 登录跳转
                Util::json(['code' => 0, 'msg' => Util::tran('登录成功'), 'token' => $token, 'login' => $this->checkLogin()], $this->callback);
                break;

                // 忘记密码
            case 'forget':
                $this->login && Util::json(['code' => 1, 'msg' => Util::tran('你已经登录')], $this->callback);
                $ip = Util::getIp();
                $param = Util::param(['email', 'captcha', 'password', 'repeat_password', 'verify']);
                empty($param['email']) && Util::json(['code' => 1, 'msg' => Util::tran('请输入邮箱地址')], $this->callback);
                !filter_var($param['email'], FILTER_VALIDATE_EMAIL) && Util::json(['code' => 1, 'msg' => Util::tran('邮箱格式错误')], $this->callback);
                $user = Model::fetch("SELECT * FROM `user` WHERE `email`=:email", [':email' => $param['email']]);
                !$user && Util::json(['code' => 1, 'msg' => '邮箱不存在或未注册'], $this->callback);
                empty($param['verify']) || !in_array($param['verify'], ['send', 'reset']) && Util::json(['code' => 1, 'msg' => Util::tran('字段错误')], $this->callback);
                if (!session_id()) session_start();

                // 发送验证码
                if ($param['verify'] == 'send') {
                    $verycode = mt_rand(100000, 999999);
                    $_SESSION['email_verycode'] = [
                        'code' => $verycode,
                        'email' => $param['email'],
                        'expire' => time() + 600 // 10分钟有效期
                    ];
                    (empty($this->setting['email']) || empty($this->setting['code']) || empty($this->setting['service'])) && Util::json(['code' => 1, 'msg' => 'SMTP ' . Util::tran('配置错误')], $this->callback);
                    $sendmail = Util::sendMail($param['email'], Util::tran('您的验证码'), Util::tran('您的验证码') . ': ' . $verycode . ' ' . Util::tran('验证码') . ' 10 ' . Util::tran('分钟内有效'), $this->setting['email'], $this->setting['code'], $this->setting['service']);
                    if ($sendmail === true) return Util::json(['code' => 0, 'msg' => Util::tran('验证邮件已发送至') . ' ' . $param['email']], $this->callback);
                    Util::json(['code' => 1, 'msg' => $sendmail], $this->callback);
                }

                // 重置密码
                if ($param['verify'] == 'reset') {
                    if (!isset($_SESSION['email_verycode']) || !isset($_SESSION['email_verycode']['code']) || !isset($_SESSION['email_verycode']['email']) || empty($param['captcha']) || $_SESSION['email_verycode']['code'] != $param['captcha'] || empty($_SESSION['email_verycode']['expire']) || $_SESSION['email_verycode']['expire'] < time())
                        Util::json(['code' => 1, 'msg' => Util::tran('验证码错误或已过期')], $this->callback);
                    if (empty($param['password']) || strlen($param['password']) < 5)
                        Util::json(['code' => 1, 'msg' => Util::tran('密码至少位数') . ' 5'], $this->callback);
                    if ($param['password'] !== $param['repeat_password'])
                        Util::json(['code' => 1, 'msg' => Util::tran('两次密码输入不一致')], $this->callback);
                    unset($_SESSION['email_verycode']);

                    // 清除来源页缓存
                    if (isset($_SERVER["HTTP_REFERER"])) {
                        $file_name = md5($_SERVER["HTTP_REFERER"]);
                        if ($pos = strpos($_SERVER["HTTP_REFERER"], 'redirect_to=')) {
                            $file_name = md5(substr($_SERVER["HTTP_REFERER"], $pos + 12));
                        }
                        $cache_file = IA_ROOT . '/mvc/tpl/' . substr($file_name, 0, 2) . '/' . $file_name . '.php';
                        is_file($cache_file) && unlink($cache_file);
                    }

                    // 清除来源页缓存
                    $this->clearOrgin();

                    // 写入数据库记录日志
                    $rowCount = Model::update('user', ['password' => md5(base64_encode($param['password'])), 'remember_token' => ''], ['email' => $param['email']]);
                    if ($rowCount) {
                        Util::config('log') && Model::insert('log', ['author' => $user['username'], 'request_method' => 'ajax', 'url' => htmlentities($_SERVER['REQUEST_URI']), 'title' => Util::tran('重置密码'), 'message' => Util::tran('重置密码') . " {$user['username']} ({$user['email']}) " . Util::tran('成功'), 'ip' => $ip, 'status' => 0, 'time' => time()]);
                        Util::json(['code' => 0, 'msg' => Util::tran('密码重置成功') . ', ' . Util::tran('请重新登录')], $this->callback);
                    } else {
                        Util::config('log') && Model::insert('log', ['author' => $user['username'], 'request_method' => 'ajax', 'url' => htmlentities($_SERVER['REQUEST_URI']), 'title' => Util::tran('重置密码'), 'message' => Util::tran('没有改变') . " {$user['username']} ({$user['email']}) " . Util::tran('信息'), 'ip' => $ip, 'status' => 0, 'time' => time()]);
                        Util::json(['code' => 1, 'msg' => Util::tran('没有改变信息')], $this->callback);
                    }
                }
                break;

                // 注册
            case 'register':
                $this->login && Util::json(['code' => 1, 'msg' => Util::tran('你已经注册')], $this->callback);
                $param = Util::param(['username', 'password', 'repeat_password', 'email', 'nickname', 'phone', 'remember']);
                if (empty($param['username']) || !preg_match('#^[a-z0-9_-]{3,12}$#', $param['username']))
                    Util::json(['code' => 1, 'msg' => Util::tran('用户名只能英文字母数字下划线或中划线，位数') . ' 3~12'], $this->callback);
                $check = Model::fetchColumn("SELECT `username` FROM `user` WHERE `username`=:username ORDER BY `id`", [':username' => $param['username']]);
                $check && Util::json(['code' => 1, 'msg' => Util::tran('用户名已存在')], $this->callback);
                if (empty($param['password']) || strlen($param['password']) < 5)
                    Util::json(['code' => 1, 'msg' => Util::tran('密码至少位数') . ' 5'], $this->callback);
                if ($param['password'] !== $param['repeat_password'])
                    Util::json(['code' => 1, 'msg' => Util::tran('两次密码输入不一致')], $this->callback);
                if (!isset($param['email']) || (!empty($param['email']) && !filter_var($param['email'], FILTER_VALIDATE_EMAIL)))
                    Util::json(['code' => 1, 'msg' => Util::tran('邮箱格式错误')], $this->callback);
                if (!empty($param['email'])) {
                    $check = Model::fetchColumn("SELECT `email` FROM `user` WHERE `email`=:email ORDER BY `id`", [':email' => $param['email']]);
                    $check && Util::json(['code' => 1, 'msg' => Util::tran('邮箱已存在')], $this->callback);
                }
                if (!empty($param['nickname']) && !preg_match('#^[\x{4e00}-\x{9fa5}A-Za-z0-9_-]{2,12}$#u', $param['nickname']))
                    Util::json(['code' => 1, 'msg' => Util::tran('昵称只能中英文字母数字下划线中划线，位数') . ' 2~12'], $this->callback);
                if (!empty($param['phone']) && !preg_match('#^1((34[0-8]\d{7})|((3[0-3|5-9])|(4[5-7|9])|(5[0-3|5-9])|(66)|(7[2-3|5-8])|(8[0-9])|(9[1|8|9]))\d{8})$#', $param['phone']))
                    Util::json(['code' => 1, 'msg' => Util::tran('手机号码不正确')], $this->callback);
                if (!isset($param['remember']) || !in_array((int)$param['remember'], [0, 1]))
                    $param['remember'] = 1;

                $remember_me = $param['remember'];
                $param['password'] = md5(base64_encode($param['password']));
                unset($param['repeat_password']);
                unset($param['remember']);

                // 验证手机号重复
                if (!empty($param['phone'])) {
                    $check = Model::fetchColumn("SELECT `phone` FROM `user` WHERE `phone`=:phone ORDER BY `id`", [':phone' => $param['phone']]);
                    $check && Util::json(['code' => 1, 'msg' => Util::tran('手机号码已存在')], $this->callback);
                }

                // 写入数据库记录日志
                $param['role_id'] = 2;
                $param['reg_time'] = time();
                $lastInsertId = Model::insert('user', $param);
                if ($lastInsertId) {
                    // 注册后登录
                    $_SESSION['uid'] = $lastInsertId;

                    // 记住登录
                    $token = md5($param['username'] . md5(base64_encode($param['password'])));
                    if ('1' == $remember_me) {
                        setcookie('token', $token, time() + 3600 * 24 * 30, '/');
                        Model::update('user', ['remember_token' => $token], ['id' => $lastInsertId]);
                    } else {
                        Model::update('user', ['remember_token' => ''], ['id' => $lastInsertId]);
                    }

                    // 单点登录
                    $setting = Model::fetch('SELECT * FROM `setting` WHERE id=:id ORDER BY `id` ASC LIMIT 1', [':id' => 1]);
                    if ($setting && ($check = json_decode($setting['value'], true)) && isset($check['alone_login']) && $check['alone_login']) {
                        $randstr = md5($token . rand(100000, 999999));
                        Util::setCache('rand' . $lastInsertId, $randstr);
                        setcookie('randstr', $randstr, time() + 3600 * 24 * 30, '/');
                    }

                    // 更新时间
                    $user = Model::fetch('SELECT * FROM `user` WHERE `id`=:id', [':id' => $lastInsertId]);
                    !$user['first_login'] && Model::update('user', ['first_login' => time()], ['id' => $user['id']]);
                    Model::update('user', ['last_login' => time()], ['id' => $user['id']]);

                    // 清除来源页缓存
                    if (isset($_SERVER["HTTP_REFERER"])) {
                        $file_name = md5($_SERVER["HTTP_REFERER"]);
                        if ($pos = strpos($_SERVER["HTTP_REFERER"], 'redirect_to=')) {
                            $file_name = md5(substr($_SERVER["HTTP_REFERER"], $pos + 12));
                        }
                        $cache_file = IA_ROOT . '/mvc/tpl/' . substr($file_name, 0, 2) . '/' . $file_name . '.php';
                        is_file($cache_file) && unlink($cache_file);
                    }

                    // 清除来源页缓存
                    $this->clearOrgin();
                    Util::config('log') && Model::insert('log', ['author' => $param['username'], 'request_method' => Util::requestMethod(), 'url' => htmlentities($_SERVER['REQUEST_URI']), 'title' => Util::tran('前端注册'), 'message' => Util::tran('前端注册') . " {$param['username']} " . Util::tran('成功'), 'ip' => Util::getIp(), 'status' => 0, 'time' => time()]);
                    Util::json(['code' => 0, 'msg' => Util::tran('注册成功'), 'token' => $token, 'login' => $this->checkLogin()], $this->callback);
                } else {
                    Util::config('log') && Model::insert('log', ['author' => $param['username'], 'request_method' => Util::requestMethod(), 'url' => htmlentities($_SERVER['REQUEST_URI']), 'title' => Util::tran('前端注册'), 'message' => Util::tran('前端注册') . " {$param['username']} " . Util::tran('失败'), 'ip' => Util::getIp(), 'status' => 1, 'time' => time()]);
                    Util::json(['code' => 1, 'msg' => Util::tran('注册失败'), 'token' => '', 'login' => 0], $this->callback);
                }
                break;

                // 登出
            case 'logout':
                !$this->login && Util::json(['code' => 1, 'msg' => Util::tran('你已登出')], $this->callback);

                // 清除token
                Model::update('user', ['remember_token' => ''], ['id' => $this->login['id']]);

                // 记录日志
                $username = Model::fetchColumn("SELECT `username` FROM `user` WHERE `id`=:id ORDER BY `id` ASC LIMIT 1", ['id' => $this->login['id']]);
                Util::config('log') && Model::insert('log', ['author' => $username, 'request_method' => Util::requestMethod(), 'url' => htmlentities($_SERVER['REQUEST_URI']), 'title' => Util::tran('前端登出'), 'message' => Util::tran('登出成功'), 'ip' => Util::getIp(), 'status' => 1, 'time' => time()]);

                // 清除session和cookie
                unset($_SESSION['uid']);
                setcookie('token', '', time() - 3600, '/');
                setcookie('randstr', '', time() - 3600, '/');
                Util::delCache('rand' . $this->login['id']);

                // 清除来源页缓存
                $this->clearOrgin();
                Util::json(['code' => 0, 'msg' => Util::tran('登出成功'), 'token' => '', 'login' => 0], $this->callback);
                break;

                // 分享到邮件
            case 'share_email':
                $param = Util::param(['post_id', 'email']);
                empty($param['email']) && Util::json(['code' => 1, 'msg' => Util::tran('请输入邮箱地址')], $this->callback);
                !filter_var($param['email'], FILTER_VALIDATE_EMAIL) && Util::json(['code' => 1, 'msg' => Util::tran('邮箱格式错误')], $this->callback);
                $row = Model::fetch("SELECT * FROM `article` WHERE `status` = 0  AND `id` = :id ORDER BY `id` LIMIT 1", [':id' => empty($param['post_id']) ? 0 : (int)$param['post_id']]);
                empty($row) && Util::json(['code' => 1, 'msg' => Util::tran('非法文章ID')], $this->callback);
                (empty($this->setting['email']) || empty($this->setting['code']) || empty($this->setting['service'])) && Util::json(['code' => 1, 'msg' => 'SMTP ' . Util::tran('配置错误')], $this->callback);
                $row = $this->data([$row])[0];
                $id_or_slug = isset($row['slug']) && $row['slug'] ? $row['slug'] : $param['post_id'];
                $link = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . $id_or_slug . '.html';
                $title = $row['title'];
                $description = $row['description'];
                $entry_content = $row['content'];
                if ($this->cdn_speed) {
                    $entry_content = preg_replace('#(\ssrc=["\'][^"\']*["\'])#i', '', $entry_content);
                    $entry_content = preg_replace('#(\sdata-src)=(["\'][^"\']*["\'])#i', ' src=$2', $entry_content);
                }
                if ($this->lang === 'zh-TW') {
                    require IA_ROOT . '/util/zh-tranTW.php';
                    $title = zh_tranTW($title);
                    $description = zh_tranTW($description);
                    $entry_content = zh_tranTW($entry_content);
                }
                $entry_content = ($description ? '<p class="lead">' . strip_tags($description) . '</p>' : '') . ' ' . stripslashes($entry_content) . '<p>' . sprintf('Time: %1$s<br />URL: <a href="%2$s" title="%3$s">%2$s</a>', date('Y-m-d H:i:s', time()), $link, strip_tags($title)) . '</p>';
                $sendmail = Util::sendMail($param['email'], $title, $entry_content, $this->setting['email'], $this->setting['code'], $this->setting['service']);
                if ($sendmail === true) return Util::json(['code' => 0, 'msg' => Util::tran('文章已发送到') . ' ' . $param['email']], $this->callback);
                Util::json(['code' => 1, 'msg' => $sendmail], $this->callback);
                break;

                // ajax获取点赞收藏的文章
            case 'page_lists':
                $param = Util::param(['ids', 'p', 'slug']);
                $lang = $this->lang == 'zh-TW' ? 'zh-CN' : $this->lang;
                $p = max((empty($param['p']) ? 0 : (int)$param['p']), 1);
                $start = ($p - 1) * $this->limit;
                $ids = empty($param['ids']) ? [] : (array)$param['ids'];
                $idStr = isset($param['ids']) && $param['ids'] ? implode(',', $ids) : '0';
                $count = Model::fetchColumn("SELECT COUNT(*) FROM `article` WHERE `lang` = '{$lang}' AND `status` = 0 AND `id` IN ($idStr)");
                $data = Model::fetchAll("SELECT * FROM `article` WHERE `lang` = '{$lang}' AND `status` = 0 AND `id` IN ($idStr) LIMIT $start, $this->limit");
                $arrs = [];
                foreach ($ids as $id) {
                    $item = array_filter($data, function ($item) use ($id) {
                        return $item['id'] == $id;
                    });
                    if ($item) {
                        $arrs[] = array_shift($item);
                    }
                }
                $noArr = array_filter($data, function ($item) use ($ids) {
                    return !in_array($item['id'], $ids);
                });
                foreach ($noArr as $item) {
                    $arrs[] = $item;
                }
                $data = $this->data($arrs);
                $base = empty($param['slug']) ? 'page/page-favorites' : "page/{$param['slug']}";
                $pager = $this->pager($count, $p, $base);
                if ($count) {
                    Util::json(['code' => 0, 'msg' => 'success!', 'data' => $data, 'count' => $count, 'pager' => $pager, 'p' => $p], $this->callback);
                } else {
                    Util::json(['code' => 1, 'msg' => Util::tran('还没有文章')], $this->callback);
                }
                break;

                // ajax评论
            case 'comment':
                $param = Util::param(['post_id', 'pid', 'comment', 'anti_spam']);
                empty($param['post_id']) && Util::json(['code' => 1, 'msg' => 'ID' . Util::tran('非法')], $this->callback);
                $this->setting['can_comment'] && !$this->login && Util::json(['code' => 1, 'msg' => Util::tran('请先登录')], $this->callback);
                $comment_text = trim(htmlentities(preg_replace('/\s+/sim', ' ', strip_tags(empty($param['comment']) ? '' : $param['comment']))));
                (empty($comment_text) || strlen($comment_text) < 2) && Util::json(['code' => 1, 'msg' => Util::tran('请写点评论')], $this->callback);
                $can_passed = false;
                if ($this->login && isset($param['pid']) && $param['pid'] == '0' && in_array(81, $this->login['auth_ids'])) {
                    $can_passed = true;
                }
                if ($this->login && isset($param['pid']) && $param['pid'] != '0' && in_array(83, $this->login['auth_ids'])) {
                    $can_passed = true;
                }
                $pid = empty($param['pid']) ? 0 : (int)$param['pid'];
                $check = Model::fetchColumn("SELECT `comment` FROM `comment` WHERE `comment` = :comment AND `pid` = :pid ORDER BY `id`", [':comment' => $can_passed ? $param['comment'] : $comment_text, ':pid' => $pid]);
                $check && Util::json(['code' => 1, 'msg' => empty($param['pid']) ? Util::tran('评论内容重复') : Util::tran('回复内容重复')], $this->callback);
                if ($pid > 0) {
                    $check = Model::fetchColumn("SELECT `comment` FROM `comment` WHERE `comment` = :comment AND `id` = :pid ORDER BY `id`", [':comment' => $can_passed ? $param['comment'] : $comment_text, ':pid' => $pid]);
                    $check && Util::json(['code' => 1, 'msg' => Util::tran('回复与评论内容重复')], $this->callback);
                }
                $data = [];
                $data['pid'] = isset($param['pid']) ? (int)$param['pid'] : 0;
                $data['article_id'] = isset($param['post_id']) ? $param['post_id'] : 0;
                $data['comment'] = $can_passed ? $param['comment'] : $comment_text;
                $data['author'] = $this->login ? $this->login['username'] : 'anonymous';
                $data['status'] = $can_passed ? 0 : (int)$this->setting['pending_comment'];
                // 机器人评论
                if (empty($param['anti_spam']) || $param['anti_spam'] !== md5($param['post_id'])) {
                    $data['status'] = 3;
                }
                $data['time'] = time();
                $data['lang'] = $this->lang == 'zh-TW' ? 'zh-CN' : $this->lang;
                $check = Model::fetchColumn("SELECT `id` FROM `article` WHERE `id`=:id", [':id' => $param['post_id']]);
                !$check && Util::json(['code' => 1, 'msg' => Util::tran('被评论的文章不存在')], $this->callback);
                $lastInsertId = Model::insert('comment', $data);
                if ($lastInsertId) {
                    // 查找图像
                    $user = Model::fetch("SELECT * FROM `user` WHERE `status` = 0 AND `username` = :author ORDER BY `id` LIMIT 1", [':author' => $data['author']]);
                    // 清除文章缓存
                    $this->clearCache($param['post_id']);
                    Util::json(['code' => 0, 'msg' => Util::tran('评论发表成功') . ($data['status'] == 0 ? '' : Util::tran('，等待审核') . '...'), 'data' => [
                        'id' => $lastInsertId,
                        'author' => $data['author'],
                        'nickname' => isset($user['nickname']) && $user['nickname'] ? strip_tags($user['nickname']) : $data['author'],
                        'comment' => $data['comment'],
                        'status' => $data['status'],
                        'avatar' => isset($user['avatar']) && $user['avatar'] ? (preg_match('#(/static/upload/[^\.]+\.(gif|jpe?g|png|bmp|webp|psd|svg|tiff))#i', $user['avatar'], $match) ? $this->site_url . $match[1] : $user['avatar']) : ''
                    ]], $this->callback);
                } else {
                    Util::json(['code' => 1, 'msg' => Util::tran('发表评论失败')], $this->callback);
                }
                break;

                // 删除评论
            case 'delete_comment':
                $param = Util::param(['comment_id']);
                $row = Model::fetch("SELECT `id`, `pid`, `article_id` FROM `comment` WHERE `id` = :id AND `status` = 0 ORDER BY `id` LIMIT 1", [':id' => empty($param['comment_id']) ? 0 : (int)$param['comment_id']]);
                !$row && Util::json(['code' => 1, 'msg' => 'ID ' . Util::tran('错误')], $this->callback);
                $rows = Model::fetchAll("SELECT `id` FROM `comment` WHERE `pid` = :pid AND `status` = 0 ORDER BY `id`", [':pid' => $row['id']]);
                $ids = array_map(function ($item) {
                    return $item['id'];
                }, $rows);
                array_push($ids, $row['id']);
                // 检查用户的删除权限
                if (!$this->login || ($this->login && !in_array(88, $this->login['auth_ids']))) {
                    Util::json(['code' => 1, 'msg' => Util::tran('没有权限，请联系管理员')], $this->callback);
                }
                $paths = [1 => [], 2 => []];
                $comNames = [];
                $idStr = implode(',', $ids);
                $rows = Model::fetchAll("SELECT `article_id`, `comment` FROM `comment` WHERE `id` IN ($idStr)");
                foreach ($rows as $row) {
                    $comNames[] = mb_strimwidth(preg_replace('/\s+/sim', ' ', strip_tags($row['comment'])), 0, 60, '...', 'UTF-8');
                    if (preg_match_all('#<[^>]*=(["\'])(.*?)(/static/upload/[^\s"\'<>]+?\.[^\s"\'<>]+)\1#i', $row['comment'], $matches, PREG_SET_ORDER)) {
                        foreach ($matches as $match) {
                            if (is_file(IA_ROOT . $match[3]))
                                $paths[1][] = $match[3];
                            else
                                $paths[2][] = $match[3];
                        }
                    }
                }
                $rowCount = Model::delete('comment', ['id' => array_unique($ids)]);
                if ($rowCount) {
                    // 修改媒体状态
                    $paths[1] && Model::update('media', ['status' => 1], ['path' => array_unique($paths[1])]);
                    $paths[2] && Model::update('media', ['status' => 2], ['path' => array_unique($paths[2])]);
                    // 清除文章缓存
                    $this->clearCache($row['article_id']);
                    Util::config('log') && Model::insert('log', ['author' => $this->login['username'], 'request_method' => Util::requestMethod(), 'url' => htmlentities($_SERVER['REQUEST_URI']), 'title' => Util::tran('前端删除评论'), 'message' => Util::tran('前端删除评论') . " " . implode(' ', $comNames) . " " . Util::tran('成功'), 'ip' => Util::getIp(), 'status' => 0, 'time' => time()]);
                    Util::json(['code' => 0, 'msg' => Util::tran('删除成功')], $this->callback);
                } else {
                    Util::config('log') && Model::insert('log', ['author' => $this->login['username'], 'request_method' => Util::requestMethod(), 'url' => htmlentities($_SERVER['REQUEST_URI']), 'title' => Util::tran('前端删除评论'), 'message' => Util::tran('前端删除评论') . " " . implode(' ', $comNames) . " " . Util::tran('失败'), 'ip' => Util::getIp(), 'status' => 1, 'time' => time()]);
                    Util::json(['code' => 1, 'msg' => Util::tran('删除失败')], $this->callback);
                }
                break;

                // 下载内容
            case 'download':
                $param = Util::param(['post_id']);
                $row = Model::fetch("SELECT * FROM `article` WHERE `status` = 0  AND `id` = :id ORDER BY `id` LIMIT 1", [':id' => empty($param['post_id']) ? 0 : (int)$param['post_id']]);
                empty($row) && Util::json(['code' => 1, 'msg' => Util::tran('非法文章ID')], $this->callback);
                $row = $this->data([$row])[0];
                $id_or_slug = isset($row['slug']) && $row['slug'] ? $row['slug'] : $param['post_id'];
                $link = $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . $id_or_slug . '.html';
                $title = $row['title'];
                $description = $row['description'];
                $entry_content = $row['content'];
                // 繁体中文，字典替换
                if ($this->lang === 'zh-TW') {
                    require IA_ROOT . '/util/zh-tranTW.php';
                    $title = zh_tranTW($title);
                    $description = zh_tranTW($description);
                    $entry_content = zh_tranTW($entry_content);
                }
                // $tts_content = $row['content'];
                // $ignore = ['style', 'script', 'iframe', 'video', 'audio', 'source', 'pre', 'code', 'img', 'table', 'form', 'fieldset', 'legend', 'label', 'input', 'textarea', 'select', 'button'];
                // foreach ($ignore as $v) {
                //     $tts_content = preg_replace('/<' . $v . '[^>]*>.*?<\/' . $v . '>/sim', '', $tts_content);
                // }
                // $tts_content = preg_replace('/<br[^>]*>/sim', '。', $tts_content);
                // $tts_content = preg_replace('/<[^>]+>(.+?)<\/[^>]+>/sim', '$1。', $tts_content);
                // $tts_content = preg_replace('/<[^>]+>/sim', '', $tts_content);
                // $tts_content = preg_replace('/\s+/sim', ' ', $tts_content);
                // $tts_content = trim(preg_replace('/\。+/sim', '', strip_tags($tts_content)));
                // 内容媒体编码
                $n = preg_match_all('/<(img|video|audio|source|embed)([^>]*)src=["\'](.*?)["\']([^>]*)>/sim', $entry_content, $matches);
                for ($i = 0; $i < $n; $i++) {
                    list($src) = explode('?', $matches[3][$i]);
                    if (false !== strpos($src, preg_replace("#/{$this->lang}/$#", '', $this->site_url))) {
                        !is_file(str_replace($this->site_url, IA_ROOT, $src)) && Util::json(['code' => 1, 'msg' => Util::tran('本地媒体已失效或不存在')], $this->callback);
                        $media = $this->base64Code($src);
                        if (!$media) Util::json(['code' => 1, 'msg' => Util::tran('媒体合成') . ' ' . ($i + 1) . '/' . $n . ' ' . Util::tran('失败！请稍后再试。')], $this->callback);
                        $entry_content = str_replace($matches[0][$i], '<' . $matches[1][$i] . $matches[2][$i] . 'src="' . $media . '"' . $matches[4][$i] . '>', $entry_content);
                    }
                }
                $n = preg_match_all('/<(img|video|audio|source|embed)([^>]*)data-src=["\'](.*?)["\']([^>]*)>/sim', $entry_content, $matches);
                for ($i = 0; $i < $n; $i++) {
                    list($src) = explode('?', $matches[3][$i]);
                    if (false !== strpos($src, preg_replace("#/{$this->lang}/$#", '', $this->site_url))) {
                        !is_file(str_replace($this->site_url, IA_ROOT, $src)) && Util::errMsg(Util::tran('本地媒体已失效或不存在'));
                        $media = $this->base64Code($src);
                        if (!$media) Util::json(['code' => 1, 'msg' => Util::tran('媒体合成') . ' ' . ($i + 1) . '/' . $n . ' ' . Util::tran('失败！请稍后再试。')], $this->callback);
                        $entry_content = str_replace($matches[0][$i], '<' . $matches[1][$i] . $matches[2][$i] . 'data-src="' . $media . '"' . $matches[4][$i] . '>', $entry_content);
                    }
                }
                // 合成语音编码
                // $ttsUrl = 'https://dict.youdao.com/dictvoice?le=zh&audio=';
                // $ttsUrl = 'https://translate.googleapis.com/translate_tts?ie=UTF-8&tl=zh-CN&tk=663060.841892&client=webapp&q=';
                // 不支持日语，土耳其语，英语，韩语
                // $ttsUrl = 'https://tts.baidu.com/text2audio?cuid=baike&lan=zh&ctp=1&pdt=301&vol=9&rate=32&per=0&tex=';
                // 不支持德语，日语，阿拉伯语，土耳其语，韩语，法语
                // $ttsUrl = 'https://fanyi.baidu.com/gettts?lan=zh&spd=5&source=web&text=';
                // 不支持日语，俄语，阿拉伯语，泰语，韩语
                // $ttsUrl = 'https://fanyi.sogou.com/reventondc/synthesis?speed=1&lang=zh-CHS&from=translateweb&speaker=6&text=';
                // 保持停顿等距分割
                // $ttsArr = $this->ttsStrSplit($tts_content, preg_match('/\/\/[\w]+\.google/', $ttsUrl) ? 200 : 500);
                // $len = count($ttsArr);
                // for ($i = 0; $i < $len; $i++) {
                //     parse_str(substr(strstr($ttsUrl . urlencode($ttsArr[$i]), '?'), 1), $data);
                //     $tts = $this->base64Code(strstr($ttsUrl, '?', true), $data, 'POST');
                //     if (!$tts) Util::errMsg(Util::tran('语音合成') . ' ' . ($i + 1) . '/' . $len . ' ' . Util::tran('失败！请稍后再试。'));
                //     $ttsArr[$i] = $tts;
                // }
                // $tts_content = json_encode($ttsArr);
                !is_dir(IA_ROOT . '/static/upload/articles/') && mkdir(IA_ROOT . '/static/upload/articles/', 0700, true);
                $file = '/static/upload/articles/ID' . str_pad($param['post_id'], 7, '0', STR_PAD_LEFT) . '-' . str_replace('/', '-', $this->lang == 'zh-CN' ? '' : $this->lang . '/') . $id_or_slug . '.html';
                $handle = fopen(IA_ROOT . $file, "w");
                fwrite($handle, '<!DOCTYPE html>
<html lang="' . $this->lang . '">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>' . strip_tags($title) . '</title>
    <link rel="shortcut icon" href="data:image/svg+xml;utf8,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' fill=\'%23673ab7\' viewBox=\'0 0 16 16\'%3E%3Cpath d=\'M6.375 7.125V4.658h1.78c.973 0 1.542.457 1.542 1.237 0 .802-.604 1.23-1.764 1.23zm0 3.762h1.898c1.184 0 1.81-.48 1.81-1.377 0-.885-.65-1.348-1.886-1.348H6.375z\'/%3E%3Cpath d=\'M4.002 0a4 4 0 0 0-4 4v8a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4zm1.06 12V3.545h3.399c1.587 0 2.543.809 2.543 2.11 0 .884-.65 1.675-1.483 1.816v.1c1.143.117 1.904.931 1.904 2.033 0 1.488-1.084 2.396-2.888 2.396z\'/%3E%3C/svg%3E">
    <link rel="icon" href="data:image/svg+xml;utf8,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' fill=\'%23673ab7\' viewBox=\'0 0 16 16\'%3E%3Cpath d=\'M6.375 7.125V4.658h1.78c.973 0 1.542.457 1.542 1.237 0 .802-.604 1.23-1.764 1.23zm0 3.762h1.898c1.184 0 1.81-.48 1.81-1.377 0-.885-.65-1.348-1.886-1.348H6.375z\'/%3E%3Cpath d=\'M4.002 0a4 4 0 0 0-4 4v8a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4zm1.06 12V3.545h3.399c1.587 0 2.543.809 2.543 2.11 0 .884-.65 1.675-1.483 1.816v.1c1.143.117 1.904.931 1.904 2.033 0 1.488-1.084 2.396-2.888 2.396z\'/%3E%3C/svg%3E">
    ' . ($this->lang === 'mni-Mtei' ? '<link href="https://fonts.googleapis.com/css?family=Noto Sans Meetei Mayek" rel="stylesheet">' : '') . '
    <style type="text/css">
        <!--
        *{margin:0;padding:0;box-sizing:border-box}
        html{text-align:center}
        body{text-align:left;color:#333;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif' . ($this->lang === 'mni-Mtei' ? ',"Noto Sans Meetei Mayek"' : '') . '}
        body>svg{position:absolute;width:0;height:0;overflow:hidden}
        .container{max-width:980px;margin:20px auto;padding:0 20px}
        .container>*{margin:15px 0}
        .container .icon{display:inline-block;width:1em;height:1em;stroke-width:0;stroke:currentColor;fill:currentColor;color:#333}
        .entry-title{text-align:center;margin:15px 0}
        .entry-toolbar{margin:15px 0}
        .entry-toolbar button{width:40px;height:40px}
        .entry-toolbar button:hover{cursor:pointer}
        .entry-toolbar button.not-allowed:hover{cursor:not-allowed}
        .entry-toolbar .left button svg,.entry-toolbar .right button{font-size:22px}
        .entry-toolbar .left,.entry-toolbar .left button{float:left;margin-right:5px}
        .entry-toolbar .right,.entry-toolbar .right button{float:right;margin-left:5px}
        .entry-toolbar button.invisible{display:none}
        .entry-toolbar button.visible{display:block}
        .entry-toolbar .clear{clear:both}
        .entry-content >*{margin:15px 0}
        .entry-content .lead{font-size:22px}
        .entry-content .img-thumbnail{display:block;max-width:100%;width:auto;clear:both;margin:15px 0;border:1px solid #ddd;border-radius:4px;padding:4px}
        .entry-content ul, .entry-content ol{list-style-position:inside}
        .entry-content>.tts{margin:0;padding:0;border:0;border-radius:0;background-color:#fde6e0}
        .entry-content>div.tts, .entry-content>p.tts{margin:15px 0;padding:10px;border:1px solid #dcd6d6;border-radius:3px;background-color:#f9f2f4}
        .entry-content pre.prettyprint{display:block;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:0;border-radius:4px;border-left:3px solid #ccc;padding:.5em;font-size:110%;font-family:Consolas,Monaco,"Bitstream Vera Sans Mono","Courier New",Courier,monospace;margin:1em 0;white-space:pre;overflow-x:auto}
        .entry-footer{margin:15px 0 25px;text-align:left;font-size:14px;color:#999}
        .entry-footer a,.entry-footer a:hover,.entry-footer a:visited{color:#999}
        .turn-round{-webkit-animation:turn-around 1.5s linear infinite;-moz-animation:turn-around 1.5s linear infinite;-ms-animation:turn-around 1.5s linear infinite;-o-animation:turn-around 1.5s linear infinite;animation:turn-around 1.5s linear infinite}
        @-webkit-keyframes turn-round{from{-webkit-transform:rotate(0)} to{-webkit-transform:rotate(360deg)}}
        @-moz-keyframes turn-round{from{-moz-transform:rotate(0)} to{-moz-transform:rotate(360deg)}}
        @-ms-keyframes turn-round{from{-ms-transform:rotate(0)} to{-ms-transform:rotate(360deg)}}
        @-o-keyframes turn-round{from{-o-transform:rotate(0)} to{-o-transform:rotate(360deg)}}
        @keyframes turn-around{from{-webkit-transform:rotate(0);-moz-transform:rotate(0);-ms-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0)} to{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-ms-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}
        -->
    </style>
</head>
<body>
    <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
        <defs>
            <symbol id="icon-headphones" viewBox="0 0 32 32">
                <path d="M9 18h-2v14h2c0.55 0 1-0.45 1-1v-12c0-0.55-0.45-1-1-1z"></path>
                <path d="M23 18c-0.55 0-1 0.45-1 1v12c0 0.55 0.45 1 1 1h2v-14h-2z"></path>
                <path d="M32 16c0-8.837-7.163-16-16-16s-16 7.163-16 16c0 1.919 0.338 3.759 0.958 5.464-0.609 1.038-0.958 2.246-0.958 3.536 0 3.526 2.608 6.443 6 6.929v-13.857c-0.997 0.143-1.927 0.495-2.742 1.012-0.168-0.835-0.258-1.699-0.258-2.584 0-7.18 5.82-13 13-13s13 5.82 13 13c0 0.885-0.088 1.749-0.257 2.584-0.816-0.517-1.745-0.87-2.743-1.013v13.858c3.392-0.485 6-3.402 6-6.929 0-1.29-0.349-2.498-0.958-3.536 0.62-1.705 0.958-3.545 0.958-5.465z"></path>
            </symbol>
            <symbol id="icon-play" viewBox="0 0 32 32">
                <path d="M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM16 29c-7.18 0-13-5.82-13-13s5.82-13 13-13 13 5.82 13 13-5.82 13-13 13zM12 9l12 7-12 7z"></path>
            </symbol>
            <symbol id="icon-pause" viewBox="0 0 32 32">
                <path d="M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM16 29c-7.18 0-13-5.82-13-13s5.82-13 13-13 13 5.82 13 13-5.82 13-13 13zM10 10h4v12h-4zM18 10h4v12h-4z"></path>
            </symbol>
            <symbol id="icon-stop" viewBox="0 0 32 32">
                <path d="M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM16 29c-7.18 0-13-5.82-13-13s5.82-13 13-13 13 5.82 13 13-5.82 13-13 13zM10 10h12v12h-12z"></path>
            </symbol>
            <symbol id="icon-top" viewBox="0 0 32 32">
                <path d="M0 16c0 8.837 7.163 16 16 16s16-7.163 16-16-7.163-16-16-16-16 7.163-16 16zM29 16c0 7.18-5.82 13-13 13s-13-5.82-13-13 5.82-13 13-13 13 5.82 13 13z"></path>
                <path d="M22.086 20.914l2.829-2.829-8.914-8.914-8.914 8.914 2.828 2.828 6.086-6.086z"></path>
            </symbol>
            <symbol id="icon-mobile" viewBox="0 0 32 32">
                <path d="M23 0h-14c-1.65 0-3 1.35-3 3v26c0 1.65 1.35 3 3 3h14c1.65 0 3-1.35 3-3v-26c0-1.65-1.35-3-3-3zM12 1.5h8v1h-8v-1zM16 30c-1.105 0-2-0.895-2-2s0.895-2 2-2 2 0.895 2 2-0.895 2-2 2zM24 24h-16v-20h16v20z"></path>
            </symbol>
            <symbol id="icon-spinner" viewBox="0 0 32 32">
                <path d="M16 32c-4.274 0-8.292-1.664-11.314-4.686s-4.686-7.040-4.686-11.314c0-3.026 0.849-5.973 2.456-8.522 1.563-2.478 3.771-4.48 6.386-5.791l1.344 2.682c-2.126 1.065-3.922 2.693-5.192 4.708-1.305 2.069-1.994 4.462-1.994 6.922 0 7.168 5.832 13 13 13s13-5.832 13-13c0-2.459-0.69-4.853-1.994-6.922-1.271-2.015-3.066-3.643-5.192-4.708l1.344-2.682c2.615 1.31 4.824 3.313 6.386 5.791 1.607 2.549 2.456 5.495 2.456 8.522 0 4.274-1.664 8.292-4.686 11.314s-7.040 4.686-11.314 4.686z"></path>
            </symbol>
        </defs>
    </svg>
    <div class="container" id="container">
        <h1 class="entry-title">' . $title . '</h1>
        <div class="entry-toolbar">
            <div class="left">
                ' . (isset($this->setting['voice_read']) && $this->setting['voice_read'] ? '<button type="button" id="btn-headphones" class="not-allowed" disabled="disabled"><svg class="icon icon-headphones"><use xlink:href="#icon-headphones"></use></svg></button>
                <button type="button" id="btn-play" class="visible' . ($this->voice ? '' : ' not-allowed') . '"' . ($this->voice ? '' : ' disabled="disabled"') . '><svg class="icon icon-play"><use xlink:href="#icon-play"></use></svg></button>
                <button type="button" id="btn-pause" class="invisible"><svg class="icon icon-pause"><use xlink:href="#icon-pause"></use></svg></button>
                <button type="button" id="btn-stop" class="invisible"><svg class="icon icon-stop"><use xlink:href="#icon-stop"></use></svg></button>' : '<button type="button" id="btn-mobile"><a href="' . $this->site_url . '/app.html#' . str_replace($this->site_url, '', $link) . '" title="H5 ' . Util::tran('客户端') . '"><svg class="icon icon-top"><use xlink:href="#icon-mobile"></use></svg></a></button>') . '
                <button type="button" id="btn-top" class="invisible"><svg class="icon icon-top"><use xlink:href="#icon-top"></use></svg></button>
            </div>
            <div class="right">
                <button type="button" id="btn-smaller" data-size="smaller">A <sup>-</sup></button>
                <button type="button" id="btn-bigger">A <sup>+</sup></button>
            </div>
            <div class="clear"></div>
        </div>
        <div id="loading"></div>
        <div class="entry-content" id="entry-content" style="font-size:16px">
            ' . ($description ? '<p class="lead">' . strip_tags($description) . '</p>' : '') . '
            ' . stripslashes($entry_content) . '
        </div>
        <div class="entry-footer">' . sprintf('Time: %1$s<br />URL: <a href="%2$s" title="%3$s">%2$s</a>', date('Y-m-d H:i:s', time()), $link, strip_tags($title)) . '</div>
    </div>
    <script type="text/javascript">
        /* <![CDATA[ */
        var render = {
            stopped: true,
            audioArray: [],
            audio: new Audio(),
            textArray: [],
            textFontSize: 16,
            timer: null,
            nodes: [],
            speechInstance: null,
            offLine: false,
            api: "",
            playErrors: 0,
            playErrorTimer: null,
            init: function () {
                var nodes = document.getElementById("entry-content").childNodes;
                var ignore = ["STYLE", "SCRIPT", "IFRAME", "VIDEO", "AUDIO", "SOURCE", "PRE", "CODE", "IMG", "BR", "HR", "TABLE", "FORM", "FIELDSET", "LEGEND", "LABEL", "INPUT", "TEXTAREA", "SELECT", "BUTTON"];
                var meta = document.createElement("meta");
                meta.setAttribute("name", "referrer");
                meta.setAttribute("content", "same-origin");
                document.head.appendChild(meta).parentNode.removeChild(meta);
                for (var i = 0; i < nodes.length; i++) {
                    var node = nodes.item(i);
                    if (ignore.indexOf(node.tagName) === -1 && node.textContent.trim()) {
                        render.nodes.push({ node: node, index: i });
                    }
                }
                if ("speechSynthesis" in window) {
                    var i = 0;
                    var timer = setInterval(function () {
                        i++;
                        var voices = window.speechSynthesis.getVoices();
                        if (voices.length || i > 10) {
                            clearInterval(timer);
                            timer = null;
                            for (var j = 0; j < voices.length; j++) {
                                if (voices[j].lang.indexOf("' . $this->lang . '") !== -1) {
                                    render.offLine = voices[j].lang;
                                    break;
                                }
                            }
                        }
                    }, 100);
                }
                window.addEventListener("click", render.click);
                window.addEventListener("scroll", render.scroll);
                window.addEventListener("load", render.scroll);
            },
            bigger: function () {
                if (render.textFontSize <= 24) {
                    render.textFontSize += 2;
                }
                document.getElementById("entry-content").style.cssText = "font-size:" + render.textFontSize + "px";
            },
            smaller: function () {
                if (render.textFontSize >= 14) {
                    render.textFontSize -= 2;
                }
                document.getElementById("entry-content").style.cssText = "font-size:" + render.textFontSize + "px";
            },
            getTk: function(a, t) {
                var gtk = t ? "320305.131321201" : "406398.2087938574";
                var e = gtk.split("."), h = Number(e[0]) || 0, g = [], d = 0, i, j;
                for (var f = 0; f < a.length; f++) {
                    var c = a.charCodeAt(f);
                    128 > c ? g[d++] = c : (2048 > c ? g[d++] = c >> 6 | 192 : (55296 == (c & 64512) && f + 1 < a.length && 56320 == (a.charCodeAt(f + 1) & 64512) ? (c = 65536 + ((c & 1023) << 10) + (a.charCodeAt(++f) & 1023), g[d++] = c >> 18 | 240, g[d++] = c >> 12 & 63 | 128) : g[d++] = c >> 12 | 224, g[d++] = c >> 6 & 63 | 128), g[d++] = c & 63 | 128)
                }
                a = h;
                for (var d = 0; d < g.length; d++) {
                    a += g[d];
                    i = "+-a^+6";
                    for (var k = 0; k < i.length - 2; k += 3) {
                        j = i.charAt(k + 2);
                        j = "a" <= j ? j.charCodeAt(0) - 87 : Number(j);
                        j = "+" == i.charAt(k + 1) ? a >>> j : a << j;
                        a = "+" == i.charAt(k) ? a + j & 4294967295 : a ^ j;
                    }
                }
                i = "+-3^+b+-f";
                for (var k = 0; k < i.length - 2; k += 3) {
                    j = i.charAt(k + 2);
                    j = "a" <= j ? j.charCodeAt(0) - 87 : Number(j);
                    j = "+" == i.charAt(k + 1) ? a >>> j : a << j;
                    a = "+" == i.charAt(k) ? a + j & 4294967295 : a ^ j;
                }
                a ^= Number(e[1]) || 0;
                0 > a && (a = (a & 2147483647) + 2147483648);
                a %= 1E6;
                return a.toString() + "." + (a ^ h);
            },
            playEndedHandler: function () {
                render.playErrors = 0;
                if (render.textArray.length > 0) {
                    if (!render.offLine) {
                        render.audio.src = render.audioArray.pop();
                        render.audio.play();
                    }
                    var reader = render.textArray.shift();
                    if (render.offLine) {
                        render.speechInstance.text = reader.text;
                        window.speechSynthesis.speak(render.speechInstance);
                    }
                    var elem = null;
                    for (var i = 0; i < render.nodes.length; i++) {
                        if (reader.index == render.nodes[i].index) {
                            if (render.nodes[i].node.nodeType === 3) {
                                var wrapper = document.createElement("span");
                                render.nodes[i].node.parentNode.insertBefore(wrapper, render.nodes[i].node);
                                render.nodes[i].node.parentNode.removeChild(render.nodes[i].node);
                                wrapper.appendChild(render.nodes[i].node);
                                render.nodes[i].node = wrapper;
                            }
                            elem = render.nodes[i].node;
                            if (render.nodes[i].node.className) {
                                render.nodes[i].node.className += render.nodes[i].node.className.match(/^tts$|^tts\s+|\s+tts/) ? "" : " tts";
                            } else {
                                render.nodes[i].node.className = "tts";
                            }
                        } else {
                            if (render.nodes[i].node.nodeType === 1 && render.nodes[i].node.className) {
                                render.nodes[i].node.className = render.nodes[i].node.className.replace(/^tts$|^tts\s+|\s+tts/g, "");
                            }
                        }
                    }
                    var clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
                    var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
                    var pos = 0;
                    var topH = elem.offsetHeight;
                    while (elem) {
                        pos += elem.offsetTop;
                        elem = elem.offsetParent;
                    }
                    if (pos > clientHeight + scrollTop - topH) {
                        var distance = 0;
                        var timer = setInterval(function () {
                            distance += Math.ceil((pos - distance) / 10);
                            window.scrollTo(0, Math.min(distance, pos));
                            if (distance >= pos - 10) {
                                window.scrollTo(0, pos - 10);
                                clearInterval(timer);
                                timer = null;
                            }
                        }, 10);
                    }
                } else {
                    render.stop();
                    setTimeout(function () {
                        if (render.nodes[render.nodes.length - 1].node.nodeType === 3) {
                            var wrapper = document.createElement("span");
                            render.nodes[render.nodes.length - 1].node.parentNode.insertBefore(wrapper, render.nodes[render.nodes.length - 1].node);
                            render.nodes[render.nodes.length - 1].node.parentNode.removeChild(render.nodes[render.nodes.length - 1].node);
                            wrapper.appendChild(render.nodes[render.nodes.length - 1].node);
                            render.nodes[render.nodes.length - 1].node = wrapper;
                        }
                        elem = render.nodes[render.nodes.length - 1].node;
                        var distance = 0;
                        while (elem) {
                            distance += elem.offsetTop;
                            elem = elem.offsetParent;
                        }
                        var timer = setInterval(function () {
                            distance += Math.floor((0 - distance) / 10);
                            window.scrollTo(0, Math.max(distance, 0));
                            if (distance <= 0) {
                                window.scrollTo(0, 0);
                                clearInterval(timer);
                                timer = null;
                            }
                        }, 10);
                    }, 1000);
                }
            },
            playErrorHandler: function () {
                render.playErrorTimer && clearTimeout(render.playErrorTimer);
                render.playErrorTimer = setTimeout(function () {
                    if (render.playErrors % 10 < 9) {
                        render.playErrors++;
                        render.audio.src = render.audio.src.split("&t=")[0] + "&t=" + new Date().getTime();
                        render.audio.play();
                    } else {
                        clearTimeout(render.playErrorTimer);
                        render.playErrorTimer = null;
                        render.playErrors = 0;
                        document.getElementById("loading").innerHTML = "<p>Error! Audio error code: " + render.audio.error.code + "</p>";
                        setTimeout(function() {
                            render.stop();
                        }, 5000);
                    }
                }, 3000 + Math.ceil(Math.random() * 2000));
            },
            play: function () {
                document.getElementById("btn-play").setAttribute("class", "invisible");
                document.getElementById("btn-pause").setAttribute("class", "visible");
                document.getElementById("btn-stop").setAttribute("class", "visible");
                if (render.stopped) {
                    if (render.nodes.length < 1) {
                        return false;
                    }
                    document.getElementById("loading").innerHTML = "<p><svg class=\"icon turn-round\"><use xlink:href=\"#icon-spinner\"></use></svg> ' . Util::tran('请稍后') . ' ...</p>";
                    var icon = new Image();
                    icon.src = "https://translate.google.com/favicon.ico?t=" + new Date().getTime();
                    var timer = setTimeout(function () {
                        icon.onerror = icon.onload = null;
                        baidu_fanyi_tts();
                    }, 3000);
                    icon.onerror = function () {
                        clearTimeout(timer);
                        timer = null;
                        baidu_fanyi_tts();
                    }
                    icon.onload = function () {
                        clearTimeout(timer);
                        timer = null;
                        google_translate_tts();
                    }
                    function google_translate_tts() {
                        render.offLine = false;
                        render.api = "https://translate.google.com/translate_tts?client=webapp&ie=UTF-8&tl=' . ($this->voice ? $this->voice : $this->lang) . '&q=";
                        doPlay();
                    }
                    function baidu_fanyi_tts() {
                        var icon = new Image();
                        icon.src = "https://fanyi.baidu.com/favicon.ico?t=" + new Date().getTime();
                        var timer = setTimeout(function () {
                            icon.onerror = icon.onload = null;
                            if (render.offLine) {
                                doPlay();
                            } else {
                                document.getElementById("loading").innerHTML = "<p>' . Util::tran('朗读错误，请联网后再试') . ' ...</p>";
                                setTimeout(function() {
                                    render.stop();
                                }, 5000);
                            }
                        }, 3000);
                        icon.onerror = function () {
                            clearTimeout(timer);
                            timer = null;
                            if (render.offLine) {
                                doPlay();
                            } else {
                                document.getElementById("loading").innerHTML = "<p>' . Util::tran('朗读错误，请联网后再试') . ' ...</p>";
                                setTimeout(function() {
                                    render.stop();
                                }, 5000);
                            }
                        }
                        icon.onload = function () {
                            clearTimeout(timer);
                            timer = null;
                            var support = { "zh-CN": "zh", "zh-TW": "zh", en: "en", ja: "jp", ko: "kor" };
                            if (support["' . $this->lang . '"]) {
                                render.offLine = false;
                                render.api = "https://fanyi.baidu.com/gettts?lan="+ support["' . $this->lang . '"] +"&spd=5&source=web&text=";
                                doPlay();
                            } else {
                                document.getElementById("loading").innerHTML = "<p>' . Util::tran('百度语音不支持，请使用谷歌语音接口。') . '</p>";
                                document.getElementById("btn-play").setAttribute("disabled", "disabled");
                            }
                        }
                    }
                    function doPlay() {
                        var num = 200;
                        for (var i = 0; i < render.nodes.length; i++) {
                            var arrs = render.nodes[i].node.textContent.trim().split(/(\r\n|\r|\n)/g);
                            for (var j = 0; j < arrs.length; j++) {
                                var aAudioText = arrs[j].trim();
                                if (aAudioText && !render.offLine) {
                                    while (aAudioText.length >= num) {
                                        var arr = [];
                                        for (var k in aAudioText) {
                                            arr.push(aAudioText[k]);
                                            if (arr.join("").length >= num) {
                                                arr.pop();
                                                break;
                                            }
                                        }
                                        var pos = arr.join("").length;
                                        render.textArray.push({ text: aAudioText.slice(0, pos), index: render.nodes[i].index });
                                        aAudioText = aAudioText.slice(pos);
                                    }
                                }
                                aAudioText && render.textArray.push({ text: aAudioText, index: render.nodes[i].index });
                            }
                        }
                        if (render.textArray.length < 1) {
                            return false;
                        }
                        if (!render.offLine) {
                            for (var i = 0; i < render.textArray.length; i++) {
                                var address = render.api + encodeURIComponent(render.textArray[i]["text"]);
                                if (render.api.match(/\/\/[\w]+\.google/)) {
                                    address += "&tk=" + render.getTk(render.textArray[i]["text"]);
                                }
                                render.audioArray.unshift(address);
                            }
                            render.audio.preload = true;
                            render.audio.controls = true;
                            render.audio.src = render.audioArray.pop();
                            render.audio.addEventListener("ended", render.playEndedHandler);
                            render.audio.addEventListener("error", render.playErrorHandler);
                            render.audio.loop = false;
                            render.audio.play();
                        }
                        render.stopped = false;
                        var reader = render.textArray.shift();
                        if (render.offLine) {
                            render.speechInstance = new SpeechSynthesisUtterance();
                            window.speechSynthesis.cancel();
                            render.speechInstance.lang = render.offLine;
                            render.speechInstance.text = reader.text;
                            window.speechSynthesis.speak(render.speechInstance);
                            render.speechInstance.addEventListener("end", render.playEndedHandler);
                        }
                        for (var i = 0; i < render.nodes.length; i++) {
                            if (reader.index == render.nodes[i].index) {
                                if (render.nodes[i].node.nodeType === 3) {
                                    var wrapper = document.createElement("span");
                                    render.nodes[i].node.parentNode.insertBefore(wrapper, render.nodes[i].node);
                                    render.nodes[i].node.parentNode.removeChild(render.nodes[i].node);
                                    wrapper.appendChild(render.nodes[i].node);
                                    render.nodes[i].node = wrapper;
                                }
                                if (render.nodes[i].node.className) {
                                    render.nodes[i].node.className += render.nodes[i].node.className.match(/^tts$|^tts\s+|\s+tts/) ? "" : " tts";
                                } else {
                                    render.nodes[i].node.className = "tts";
                                }
                            } else {
                                if (render.nodes[i].node.nodeType === 1 && render.nodes[i].node.className) {
                                    render.nodes[i].node.className = render.nodes[i].node.className.replace(/^tts$|^tts\s+|\s+tts/g, "");
                                }
                            }
                        }
                        document.getElementById("loading").innerHTML = "";
                    }
                } else {
                    if (!render.offLine) {
                        render.audio.play();
                    } else {
                        window.speechSynthesis.resume();
                    }
                }
            },
            pause: function () {
                document.getElementById("btn-play").setAttribute("class", "visible");
                document.getElementById("btn-pause").setAttribute("class", "invisible");
                document.getElementById("btn-stop").setAttribute("class", "visible");
                document.getElementById("loading").innerHTML = "";
                if (!render.offLine) {
                    render.audio.pause();
                } else {
                    if (window.speechSynthesis.speaking) {
                        window.speechSynthesis.pause();
                    }
                }
            },
            stop: function () {
                document.getElementById("btn-play").setAttribute("class", "visible");
                document.getElementById("btn-pause").setAttribute("class", "invisible");
                document.getElementById("btn-stop").setAttribute("class", "invisible");
                document.getElementById("loading").innerHTML = "";
                if (!render.offLine) {
                    render.audio.pause();
                    render.audio.removeEventListener("ended", render.playEndedHandler);
                    render.audio.removeEventListener("error", render.playErrorHandler);
                    render.audioArray = [];
                } else {
                    window.speechSynthesis.cancel();
                    render.speechInstance.removeEventListener("end", render.playEndedHandler)
                }
                render.stopped = true;
                render.textArray = [];
                for (var i = 0; i < render.nodes.length; i++) {
                    if (render.nodes[i].node.nodeType === 1 && render.nodes[i].node.className) {
                        render.nodes[i].node.className = render.nodes[i].node.className.replace(/^tts$|^tts\s+|\s+tts/g, "");
                    }
                }
            },
            click: function (e) {
                var clickId = e.target.id || e.target.parentNode.id || e.target.parentNode.parentNode.id || "";
                clickId.match(/^btn-/) && render[clickId.slice(4)] && render[clickId.slice(4)]();
            },
            scroll: function () {
                if (render.timer) return false;
                render.timer = setTimeout(function () {
                    var clientWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
                    var clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
                    var offsetWidth = document.getElementById("container").offsetWidth;
                    var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
                    var imgs = document.getElementById("entry-content").getElementsByTagName("img");
                    var cssText = "";
                    for (var i = 0; i < imgs.length; i++) {
                        var pos = 0;
                        var img = imgs[i];
                        var elem = imgs[i];
                        while (elem) {
                            pos += elem.offsetTop;
                            elem = elem.offsetParent;
                        }
                        if (pos < clientHeight + scrollTop && img.src.match(/^data:image/) && img.getAttribute("data-src")) {
                            img.src = img.getAttribute("data-src");
                        }
                    }
                    if (scrollTop > 150) {
                        cssText = "position:fixed;right:calc(" + (clientWidth > 980 ? (clientWidth - offsetWidth) / 2 : 50) + "px - 40px);opacity:.7;";
                        document.getElementById("btn-play").style.cssText = cssText + "bottom:75px;";
                        document.getElementById("btn-pause").style.cssText = cssText + "bottom:75px;";
                        document.getElementById("btn-top").className = "visible";
                        document.getElementById("btn-top").style.cssText = cssText + "bottom:30px;";
                    } else {
                        cssText = "position:static;";
                        document.getElementById("btn-play").style.cssText = cssText;
                        document.getElementById("btn-pause").style.cssText = cssText;
                        document.getElementById("btn-top").className = "invisible";
                        document.getElementById("btn-top").style.cssText = cssText;
                    }
                    clearTimeout(render.timer);
                    render.timer = null;
                }, 500);
            },
            top: function () {
                var distance = document.documentElement.scrollTop || document.body.scrollTop;
                var timer = setInterval(function () {
                    distance += Math.floor((0 - distance) / 10);
                    window.scrollTo(0, Math.max(distance, 0));
                    if (distance <= 0) {
                        window.scrollTo(0, 0);
                        clearInterval(timer);
                        timer = null;
                    }
                }, 10);
            }
        }
        render.init();
        /* ]]> */
    </script>
</body>
</html>');
                fclose($handle);
                !is_file(IA_ROOT . $file) && Util::json(['code' => 1, 'msg' => Util::tran('合成失败，请稍后再试') . '...'], $this->callback);
                // 清除文章缓存
                $this->clearCache($param['post_id']);
                Util::json(['code' => 0, 'msg' => join('-', preg_split('/\s+/', $title)) . '-' . $this->setting['webName'], 'link' => $this->site_url . $file], $this->callback);
                break;
                // 非法请求
            default:
                Util::isAjax() ? Util::json(['code' => 1, 'msg' => Util::tran('非法的ajax请求')], $this->callback) : Util::json(['code' => 1, 'msg' => Util::tran('不合理的请求方法')], $this->callback);
        }
    }

    // base64媒体编码
    private function base64Code($url, $data = [], $type = 'GET')
    {
        $base64_code = '';
        $newFile = str_replace($this->site_url, IA_ROOT, $url);
        if (file_exists($newFile)) {
            // 本地文件
            $mime_type = '';
            if (function_exists('finfo_file')) {
                $finfo = finfo_open(FILEINFO_MIME_TYPE);
                $mime_type = finfo_file($finfo, $newFile);
                finfo_close($finfo);
            } elseif (function_exists('mime_content_type')) {
                $mime_type = mime_content_type($newFile);
            } else {
                $mime_type = Util::mimeType($newFile);
            }
            $base64_code = 'data:' . $mime_type . ';base64,' . base64_encode(file_get_contents($newFile));
        } else {
            // 网络文件
            $remote = Util::curl($url, $data, $type, [], true);
            if ($remote['result'] && $remote['mime_type']) {
                $base64_code = 'data:' . $remote['mime_type'] . ';base64,' . base64_encode($remote['result']);
            }
        }
        return $base64_code;
    }

    // tts语音字符保持停顿等距分割
    private function ttsStrSplit($string, $num = 1)
    {
        $aAudioText = '';
        $textArray = [];
        $textArr = preg_split('/;/', $string);
        for ($i = 0; $i < count($textArr); $i++) {
            if (mb_strlen(trim($textArr[$i]), 'utf8')) {
                if (mb_strlen($aAudioText, 'utf8') + mb_strlen($textArr[$i], 'utf8') < $num) {
                    $aAudioText .= $textArr[$i] . ';';
                } else {
                    while (mb_strlen($aAudioText, 'utf8') > $num) {
                        array_push($textArray, mb_substr($aAudioText, 0, $num, 'utf8'));
                        $aAudioText = mb_substr($aAudioText, $num, mb_strlen($aAudioText, 'utf8') - $num, 'utf8');
                    }
                    mb_strlen($aAudioText, 'utf8') && array_push($textArray, $aAudioText);
                    $aAudioText = $textArr[$i] . ';';
                    while (mb_strlen($aAudioText, 'utf8') > $num) {
                        array_push($textArray, mb_substr($aAudioText, 0, $num, 'utf8'));
                        $aAudioText = mb_substr($aAudioText, $num, mb_strlen($aAudioText, 'utf8') - $num, 'utf8');
                    }
                }
            }
        }
        mb_strlen($aAudioText, 'utf8') && array_push($textArray, $aAudioText);
        return $textArray;
    }

    // 清除文章缓存
    private function clearCache($article_id)
    {
        $slug = Model::fetchColumn("SELECT `slug` FROM `article` WHERE `id` = $article_id ORDER BY `id` LIMIT 1");
        $prefix = $this->site_url . ($this->lang === 'zh-CN' ? "/" : "/{$this->lang}/");
        $file_name = md5($prefix);
        $cache_file = IA_ROOT . '/mvc/tpl/' . substr($file_name, 0, 2) . '/' . $file_name . '.php';
        if (file_exists($cache_file))
            unlink($cache_file);
        $file_name = md5($prefix . ($slug ? $slug : $article_id) . '.html');
        $cache_file = IA_ROOT . '/mvc/tpl/' . substr($file_name, 0, 2) . '/' . $file_name . '.php';
        if (file_exists($cache_file))
            unlink($cache_file);
        // 评论分页缓存
        $count = Model::fetchColumn("SELECT COUNT(*) FROM `comment` WHERE `pid`=0 AND `article_id`=:article_id AND `status`=0 ORDER BY `id` DESC", [':article_id' => $article_id]);
        for ($i = 2; $i <= ceil($count / $this->limit); $i++) {
            $file_name = md5($prefix . ($slug ? $slug : $article_id) . '/page-' . $i . '.html');
            $cache_file = IA_ROOT . '/mvc/tpl/' . substr($file_name, 0, 2) . '/' . $file_name . '.php';
            if (file_exists($cache_file))
                unlink($cache_file);
        }
    }

    // 网站地图
    public function sitemap()
    {
        $lang = $this->lang == 'zh-TW' ? 'zh-CN' : $this->lang;
        $posts = Model::fetchAll("SELECT * FROM `article` WHERE `status` = 0 AND `lang` = '{$lang}' ORDER BY `id` DESC LIMIT 1000");
        array_multisort(array_map(function ($item) {
            return $item['update_at'];
        }, $posts), SORT_DESC, $posts);
        $ltime = date('Y-m-d\TH:i:s+00:00', isset($posts[0]) ? $posts[0]['update_at'] : 0);
        header('Content-type: text/xml; charset=UTF-8');
        echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
        echo "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:mobile=\"http://www.baidu.com/schemas/sitemap-mobile/1/\">\n";
        echo "<url>\n";
        echo "\t<loc>" . $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . "</loc>\n";
        echo "\t<mobile:mobile type=\"pc,mobile\" />\n";
        echo "\t<lastmod>$ltime</lastmod>\n";
        echo "\t<changefreq>daily</changefreq>\n";
        echo "\t<priority>1.0</priority>\n";
        echo "</url>\n";
        // 文章页
        foreach ($posts as $item) {
            echo "<url>\n";
            echo "\t<loc>" . $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . ($item['slug'] ? $item['slug'] : $item['id']) . ".html</loc>\n";
            echo "\t<lastmod>" . date('Y-m-d\TH:i:s+00:00', $item['update_at']) . "</lastmod>\n";
            echo "\t<changefreq>monthly</changefreq>\n";
            echo "\t<priority>0.6</priority>\n";
            echo "</url>\n";
        }
        // 独立页面
        $pages = Model::fetchAll("SELECT * FROM `page` WHERE `status` = 0 AND `lang` = '{$lang}' ORDER BY `id` DESC");
        array_multisort(array_map(function ($item) {
            return $item['time'];
        }, $pages), SORT_DESC, $pages);
        foreach ($pages as $item) {
            echo "<url>\n";
            echo "\t<loc>" . $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . "page/" . ($item['slug'] ? $item['slug'] : $item['id']) . ".html</loc>\n";
            echo "\t<lastmod>" . date('Y-m-d\TH:i:s+00:00', $item['time']) . "</lastmod>\n";
            echo "\t<changefreq>weekly</changefreq>\n";
            echo "\t<priority>0.6</priority>\n";
            echo "</url>\n";
        }
        // 分类页
        $categories = Model::fetchAll("SELECT * FROM `category` WHERE `status` = 0 AND `lang` = '{$lang}' ORDER BY `id` DESC");
        foreach ($categories as $item) {
            echo "<url>\n";
            echo "\t<loc>" . $this->site_url . '/' . ($this->lang == 'zh-CN' ? '' : $this->lang . '/') . "category/" . ($item['slug'] ? $item['slug'] : $item['id']) . ".html</loc>\n";
            echo "\t<lastmod>" . date('Y-m-d\TH:i:s+00:00', $item['time']) . "</lastmod>\n";
            echo "\t<changefreq>weekly</changefreq>\n";
            echo "\t<priority>0.8</priority>\n";
            echo "</url>\n";
        }
        echo "</urlset>\n";
        exit;
    }

    // 清除来源页缓存
    private function clearOrgin()
    {
        if (isset($_SERVER["HTTP_REFERER"])) {
            $file_name = md5($_SERVER["HTTP_REFERER"]);
            if ($pos = strpos($_SERVER["HTTP_REFERER"], 'redirect_to=')) {
                $file_name = md5(substr($_SERVER["HTTP_REFERER"], $pos + 12));
            }
            $cache_file = IA_ROOT . '/mvc/tpl/' . substr($file_name, 0, 2) . '/' . $file_name . '.php';
            is_file($cache_file) && unlink($cache_file);
        }
    }

    // 检查登录
    private function checkLogin()
    {
        $row = 0;
        $token = Util::param('token') ?: '';
        if ($token) {
            $row = Model::fetch("SELECT * FROM `user` WHERE `remember_token`=:token AND `status`=0 ORDER BY `id` ASC LIMIT 1", [':token' => urldecode('%' . implode('%', str_split($token, 2)))]);
        } else {
            if (!isset($_SESSION['uid']) || empty($_SESSION['uid'])) {
                if (!isset($_COOKIE['token']) || empty($_COOKIE['token'])) return 0;
                $row = Model::fetch("SELECT * FROM `user` WHERE `remember_token`=:token AND `status`=0 ORDER BY `id` ASC LIMIT 1", [':token' => $_COOKIE['token']]);
                if (!$row) return 0;
                $_SESSION['uid'] = $row['id'];
            }
            !$row && $row = Model::fetch("SELECT * FROM `user` WHERE `id`=:id AND `status`=0 ORDER BY `id` ASC LIMIT 1", [':id' => $_SESSION['uid']]);
        }
        if (!$row) return 0;
        $auth_ids = Model::fetchColumn("SELECT `auth_ids` FROM `role` WHERE `id`=:role_id AND `status`=0 ORDER BY `id` ASC LIMIT 1", [':role_id' => $row['role_id']]);
        $row['auth_ids'] = json_decode($auth_ids ?: '[]', true);
        unset($row['password']);
        unset($row['remember_token']);

        // 检查单点登录
        $setting = Model::fetch('SELECT * FROM `setting` WHERE id=:id ORDER BY `id` ASC LIMIT 1', [':id' => 1]);
        if ($setting && ($check = json_decode($setting['value'], true)) && isset($check['alone_login']) && $check['alone_login']) {
            $randstr = isset($_COOKIE['randstr']) ? $_COOKIE('randstr') : false;
            $cache_randstr = Util::getCache('rand' . $row['id']);
            if ($randstr !== $cache_randstr) {
                return 0;
            }
        }

        return $row;
    }
}
